lecodes-sdk 0.19.0
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.
- package/README.md +114 -0
- package/dist/global.d.ts +503 -0
- package/dist/host.d.ts +61 -0
- package/dist/inject.js +4730 -0
- package/dist/types/animate/animate.d.ts +20 -0
- package/dist/types/animate/bezier.d.ts +2 -0
- package/dist/types/animate/easings.d.ts +21 -0
- package/dist/types/canvas/Canvas.d.ts +168 -0
- package/dist/types/core/Aspect.d.ts +171 -0
- package/dist/types/core/InspectorUI.d.ts +88 -0
- package/dist/types/core/StateMachine.d.ts +81 -0
- package/dist/types/core/color.d.ts +13 -0
- package/dist/types/core/compWrite.d.ts +11 -0
- package/dist/types/core/events.d.ts +11 -0
- package/dist/types/core/fields.d.ts +58 -0
- package/dist/types/core/registry.d.ts +7 -0
- package/dist/types/core/signals.d.ts +29 -0
- package/dist/types/core/time.d.ts +31 -0
- package/dist/types/g2/Camera2D.d.ts +19 -0
- package/dist/types/g2/CharacterController2D.d.ts +134 -0
- package/dist/types/g2/Node2D.d.ts +91 -0
- package/dist/types/g2/OneWay2D.d.ts +24 -0
- package/dist/types/g2/Physics2D.d.ts +147 -0
- package/dist/types/g2/Scene2D.d.ts +72 -0
- package/dist/types/g2/Shape2D.d.ts +58 -0
- package/dist/types/g2/Sprite.d.ts +51 -0
- package/dist/types/g2/SpriteAnimation.d.ts +50 -0
- package/dist/types/g2/SpriteSheet.d.ts +69 -0
- package/dist/types/g2/Texture2D.d.ts +16 -0
- package/dist/types/g2/Tilemap.d.ts +20 -0
- package/dist/types/g2/Tileset.d.ts +32 -0
- package/dist/types/g2/Trigger2D.d.ts +20 -0
- package/dist/types/g2/autotile.d.ts +133 -0
- package/dist/types/g2/cells.d.ts +14 -0
- package/dist/types/g2/defineScene2d.d.ts +141 -0
- package/dist/types/g2/groups2d.d.ts +16 -0
- package/dist/types/g2/loop.d.ts +4 -0
- package/dist/types/g2/scenarios2d.d.ts +30 -0
- package/dist/types/g2/touch.d.ts +2 -0
- package/dist/types/gl/Camera.d.ts +79 -0
- package/dist/types/gl/CameraPlace.d.ts +26 -0
- package/dist/types/gl/CharacterController.d.ts +98 -0
- package/dist/types/gl/Gearbox.d.ts +86 -0
- package/dist/types/gl/Geometry.d.ts +39 -0
- package/dist/types/gl/IK.d.ts +53 -0
- package/dist/types/gl/InstancedMesh.d.ts +46 -0
- package/dist/types/gl/Light.d.ts +73 -0
- package/dist/types/gl/Lightmap.d.ts +85 -0
- package/dist/types/gl/Locomotion.d.ts +279 -0
- package/dist/types/gl/Material.d.ts +100 -0
- package/dist/types/gl/Mesh.d.ts +33 -0
- package/dist/types/gl/Model.d.ts +53 -0
- package/dist/types/gl/NavAgent.d.ts +99 -0
- package/dist/types/gl/NavMesh.d.ts +169 -0
- package/dist/types/gl/Node.d.ts +99 -0
- package/dist/types/gl/Noise.d.ts +20 -0
- package/dist/types/gl/Particles.d.ts +277 -0
- package/dist/types/gl/Physics.d.ts +91 -0
- package/dist/types/gl/Plane.d.ts +19 -0
- package/dist/types/gl/Ragdoll.d.ts +86 -0
- package/dist/types/gl/Ray.d.ts +8 -0
- package/dist/types/gl/Scene.d.ts +221 -0
- package/dist/types/gl/Shape.d.ts +122 -0
- package/dist/types/gl/Terrain.d.ts +280 -0
- package/dist/types/gl/Texture.d.ts +27 -0
- package/dist/types/gl/Trigger.d.ts +10 -0
- package/dist/types/gl/Vehicle.d.ts +191 -0
- package/dist/types/gl/Wheel.d.ts +95 -0
- package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
- package/dist/types/gl/animation/Animator.d.ts +219 -0
- package/dist/types/gl/animation/Layer.d.ts +31 -0
- package/dist/types/gl/animation/Loop.d.ts +17 -0
- package/dist/types/gl/animation/Playback.d.ts +36 -0
- package/dist/types/gl/animation/core.d.ts +367 -0
- package/dist/types/gl/controls.d.ts +19 -0
- package/dist/types/gl/physicsEvents.d.ts +1 -0
- package/dist/types/gl/scenarios.d.ts +98 -0
- package/dist/types/gl/state.d.ts +5 -0
- package/dist/types/gl/terrainMesh.d.ts +46 -0
- package/dist/types/gl/touch.d.ts +2 -0
- package/dist/types/inject.d.ts +123 -0
- package/dist/types/math/Mathf.d.ts +39 -0
- package/dist/types/math/mat4.d.ts +66 -0
- package/dist/types/math/quat.d.ts +53 -0
- package/dist/types/math/vec.d.ts +129 -0
- package/dist/types/net/codec.d.ts +25 -0
- package/dist/types/net/core.d.ts +106 -0
- package/dist/types/net/index.d.ts +80 -0
- package/dist/types/net/replication.d.ts +101 -0
- package/dist/types/plugins/camera.d.ts +25 -0
- package/dist/types/plugins/geolocation.d.ts +39 -0
- package/dist/types/plugins/oauth.d.ts +25 -0
- package/dist/types/plugins/permission.d.ts +1 -0
- package/dist/types/plugins/push.d.ts +46 -0
- package/dist/types/plugins/qr.d.ts +21 -0
- package/dist/types/plugins/service.d.ts +33 -0
- package/dist/types/runtime/app.d.ts +67 -0
- package/dist/types/runtime/appEvents.d.ts +7 -0
- package/dist/types/runtime/channel.d.ts +1 -0
- package/dist/types/runtime/clipboard.d.ts +7 -0
- package/dist/types/runtime/datetime.d.ts +80 -0
- package/dist/types/runtime/device.d.ts +126 -0
- package/dist/types/runtime/fetch.d.ts +31 -0
- package/dist/types/runtime/files.d.ts +33 -0
- package/dist/types/runtime/input.d.ts +121 -0
- package/dist/types/runtime/media.d.ts +46 -0
- package/dist/types/runtime/misc.d.ts +11 -0
- package/dist/types/runtime/net.d.ts +15 -0
- package/dist/types/runtime/rpc.d.ts +44 -0
- package/dist/types/runtime/service.d.ts +1 -0
- package/dist/types/runtime/share.d.ts +2 -0
- package/dist/types/runtime/storage.d.ts +5 -0
- package/dist/types/runtime/touch.d.ts +56 -0
- package/dist/types/scene/defineScene.d.ts +296 -0
- package/dist/types/scene/editorPlugins.d.ts +82 -0
- package/dist/types/scene/gizmos.d.ts +56 -0
- package/dist/types/scene/grammar.d.ts +56 -0
- package/dist/types/scene/level.d.ts +98 -0
- package/dist/types/scene/material.d.ts +76 -0
- package/dist/types/server/auth/appConfig.d.ts +16 -0
- package/dist/types/server/auth/global.d.ts +56 -0
- package/dist/types/server/auth/models.d.ts +164 -0
- package/dist/types/server/auth/types.d.ts +56 -0
- package/dist/types/server/channel.d.ts +28 -0
- package/dist/types/server/context.d.ts +27 -0
- package/dist/types/server/db/defineDb.d.ts +36 -0
- package/dist/types/server/db/fields.d.ts +146 -0
- package/dist/types/server/db/index.d.ts +6 -0
- package/dist/types/server/db/marci/query.d.ts +326 -0
- package/dist/types/server/db/types.d.ts +163 -0
- package/dist/types/server/errors.d.ts +8 -0
- package/dist/types/server/inject.d.ts +5 -0
- package/dist/types/ui/NativeView.d.ts +38 -0
- package/dist/types/ui/UI.d.ts +27 -0
- package/dist/types/ui/UIBottomSheet.d.ts +71 -0
- package/dist/types/ui/UIButton.d.ts +30 -0
- package/dist/types/ui/UIContainer.d.ts +33 -0
- package/dist/types/ui/UIImage.d.ts +35 -0
- package/dist/types/ui/UIInput.d.ts +110 -0
- package/dist/types/ui/UIModal.d.ts +70 -0
- package/dist/types/ui/UINode.d.ts +363 -0
- package/dist/types/ui/UIPager.d.ts +123 -0
- package/dist/types/ui/UIPopover.d.ts +39 -0
- package/dist/types/ui/UIScreen.d.ts +57 -0
- package/dist/types/ui/UIScrollable.d.ts +40 -0
- package/dist/types/ui/UISpacer.d.ts +9 -0
- package/dist/types/ui/UITabs.d.ts +96 -0
- package/dist/types/ui/UIText.d.ts +19 -0
- package/dist/types/ui/UIVideo.d.ts +28 -0
- package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
- package/dist/types/ui/UIWidget.d.ts +65 -0
- package/dist/types/ui/fonts.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +68 -0
- package/dist/types/ui/router.d.ts +40 -0
- package/dist/types/ui/theme.d.ts +26 -0
- package/dist/types.json +1 -0
- package/package.json +46 -0
- package/prompts/2d.md +395 -0
- package/prompts/3d-scene-files.md +113 -0
- package/prompts/3d-scene.md +119 -0
- package/prompts/3d.md +303 -0
- package/prompts/README.md +142 -0
- package/prompts/ar.md +109 -0
- package/prompts/canvas.md +27 -0
- package/prompts/compose.ts +46 -0
- package/prompts/concept.md +49 -0
- package/prompts/core-design.md +75 -0
- package/prompts/core.md +264 -0
- package/prompts/design.md +218 -0
- package/prompts/dist/2d-game.md +1530 -0
- package/prompts/dist/3d-app.md +1672 -0
- package/prompts/dist/ar-app.md +1548 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +498 -0
- package/prompts/dist/ui-app.md +1134 -0
- package/prompts/index.md +37 -0
- package/prompts/intake-prompt.md +33 -0
- package/prompts/namer-prompt.md +12 -0
- package/prompts/router-prompt.md +50 -0
- package/prompts/select.ts +111 -0
- package/prompts/ui-design.md +203 -0
- package/prompts/ui.md +803 -0
- package/src/animate/animate.ts +238 -0
- package/src/animate/bezier.ts +138 -0
- package/src/animate/easings.ts +126 -0
- package/src/bridges.d.ts +1345 -0
- package/src/canvas/Canvas.ts +305 -0
- package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
- package/src/compile/__tests__/assetMacro.test.ts +74 -0
- package/src/compile/__tests__/assetName.test.ts +55 -0
- package/src/compile/__tests__/compile.test.ts +287 -0
- package/src/compile/__tests__/detectEntry.test.ts +132 -0
- package/src/compile/__tests__/fontMacro.test.ts +199 -0
- package/src/compile/aspectMacro.ts +86 -0
- package/src/compile/assetIconMacro.ts +384 -0
- package/src/compile/assetMacro.ts +146 -0
- package/src/compile/assetName.ts +50 -0
- package/src/compile/bundler.ts +287 -0
- package/src/compile/compileProject.ts +127 -0
- package/src/compile/detectEntry.ts +128 -0
- package/src/compile/fontMacro.ts +459 -0
- package/src/compile/fontRegistry.ts +78 -0
- package/src/compile/header.ts +67 -0
- package/src/compile/index.ts +100 -0
- package/src/compile/libraryImports.ts +52 -0
- package/src/compile/liteMaterial.ts +247 -0
- package/src/compile/sceneEditor.ts +88 -0
- package/src/compile/serverSplit.ts +233 -0
- package/src/compile/serverTypes.ts +227 -0
- package/src/compile/sfnt.ts +98 -0
- package/src/compile/shaderSchema.ts +202 -0
- package/src/compile/shaderTargets.ts +77 -0
- package/src/compile/sourcemap.ts +25 -0
- package/src/core/Aspect.ts +568 -0
- package/src/core/InspectorUI.ts +212 -0
- package/src/core/StateMachine.ts +308 -0
- package/src/core/__tests__/stateMachine.test.ts +132 -0
- package/src/core/color.ts +66 -0
- package/src/core/compWrite.ts +42 -0
- package/src/core/events.ts +38 -0
- package/src/core/fields.ts +120 -0
- package/src/core/registry.ts +23 -0
- package/src/core/signals.ts +277 -0
- package/src/core/time.ts +84 -0
- package/src/g2/Camera2D.ts +40 -0
- package/src/g2/CharacterController2D.ts +276 -0
- package/src/g2/Node2D.ts +267 -0
- package/src/g2/OneWay2D.ts +66 -0
- package/src/g2/Physics2D.ts +346 -0
- package/src/g2/Scene2D.ts +209 -0
- package/src/g2/Shape2D.ts +259 -0
- package/src/g2/Sprite.ts +89 -0
- package/src/g2/SpriteAnimation.ts +171 -0
- package/src/g2/SpriteSheet.ts +166 -0
- package/src/g2/Texture2D.ts +47 -0
- package/src/g2/Tilemap.ts +41 -0
- package/src/g2/Tileset.ts +71 -0
- package/src/g2/Trigger2D.ts +77 -0
- package/src/g2/autotile.ts +433 -0
- package/src/g2/cells.ts +91 -0
- package/src/g2/defineScene2d.ts +381 -0
- package/src/g2/groups2d.ts +106 -0
- package/src/g2/loop.ts +50 -0
- package/src/g2/scenarios2d.ts +69 -0
- package/src/g2/touch.ts +83 -0
- package/src/gl/Camera.ts +160 -0
- package/src/gl/CameraPlace.ts +52 -0
- package/src/gl/CharacterController.ts +238 -0
- package/src/gl/Gearbox.ts +212 -0
- package/src/gl/Geometry.ts +279 -0
- package/src/gl/IK.ts +193 -0
- package/src/gl/InstancedMesh.ts +132 -0
- package/src/gl/Light.ts +135 -0
- package/src/gl/Lightmap.ts +402 -0
- package/src/gl/Locomotion.ts +498 -0
- package/src/gl/Material.ts +279 -0
- package/src/gl/Mesh.ts +83 -0
- package/src/gl/Model.ts +124 -0
- package/src/gl/NavAgent.ts +337 -0
- package/src/gl/NavMesh.ts +397 -0
- package/src/gl/Node.ts +350 -0
- package/src/gl/Noise.ts +30 -0
- package/src/gl/Particles.ts +676 -0
- package/src/gl/Physics.ts +222 -0
- package/src/gl/Plane.ts +53 -0
- package/src/gl/Ragdoll.ts +270 -0
- package/src/gl/Ray.ts +16 -0
- package/src/gl/Scene.ts +505 -0
- package/src/gl/Shape.ts +409 -0
- package/src/gl/Terrain.ts +1092 -0
- package/src/gl/Texture.ts +63 -0
- package/src/gl/Trigger.ts +45 -0
- package/src/gl/Vehicle.ts +473 -0
- package/src/gl/Wheel.ts +240 -0
- package/src/gl/animation/AnimationClip.ts +204 -0
- package/src/gl/animation/Animator.ts +329 -0
- package/src/gl/animation/Layer.ts +39 -0
- package/src/gl/animation/Loop.ts +31 -0
- package/src/gl/animation/Playback.ts +52 -0
- package/src/gl/animation/core.ts +670 -0
- package/src/gl/controls.ts +95 -0
- package/src/gl/physicsEvents.ts +20 -0
- package/src/gl/scenarios.ts +291 -0
- package/src/gl/state.ts +6 -0
- package/src/gl/terrainMesh.ts +219 -0
- package/src/gl/touch.ts +68 -0
- package/src/host.d.ts +61 -0
- package/src/inject.ts +203 -0
- package/src/math/Mathf.ts +118 -0
- package/src/math/mat4.ts +278 -0
- package/src/math/quat.ts +232 -0
- package/src/math/vec.ts +255 -0
- package/src/net/codec.ts +119 -0
- package/src/net/core.ts +384 -0
- package/src/net/index.ts +181 -0
- package/src/net/replication.ts +622 -0
- package/src/plugins/camera.ts +81 -0
- package/src/plugins/geolocation.ts +123 -0
- package/src/plugins/oauth.ts +61 -0
- package/src/plugins/permission.ts +7 -0
- package/src/plugins/push.ts +132 -0
- package/src/plugins/qr.ts +73 -0
- package/src/plugins/service.ts +47 -0
- package/src/runtime/app.ts +101 -0
- package/src/runtime/appEvents.ts +54 -0
- package/src/runtime/channel.ts +61 -0
- package/src/runtime/clipboard.ts +20 -0
- package/src/runtime/datetime.ts +329 -0
- package/src/runtime/device.ts +293 -0
- package/src/runtime/fetch.ts +77 -0
- package/src/runtime/files.ts +108 -0
- package/src/runtime/input.ts +175 -0
- package/src/runtime/media.ts +111 -0
- package/src/runtime/misc.ts +16 -0
- package/src/runtime/net.ts +36 -0
- package/src/runtime/rpc.ts +218 -0
- package/src/runtime/service.ts +83 -0
- package/src/runtime/share.ts +9 -0
- package/src/runtime/storage.ts +13 -0
- package/src/runtime/touch.ts +76 -0
- package/src/scene/defineScene.ts +1396 -0
- package/src/scene/editorPlugins.ts +110 -0
- package/src/scene/gizmos.ts +148 -0
- package/src/scene/grammar.ts +120 -0
- package/src/scene/level.ts +296 -0
- package/src/scene/material.ts +188 -0
- package/src/server/auth/appConfig.ts +12 -0
- package/src/server/auth/global.ts +80 -0
- package/src/server/auth/host.ts +318 -0
- package/src/server/auth/models.ts +83 -0
- package/src/server/auth/types.ts +50 -0
- package/src/server/channel.ts +56 -0
- package/src/server/context.ts +36 -0
- package/src/server/db/defineDb.ts +237 -0
- package/src/server/db/fields.ts +132 -0
- package/src/server/db/httpTransport.ts +93 -0
- package/src/server/db/index.ts +7 -0
- package/src/server/db/marci/query.ts +412 -0
- package/src/server/db/types.ts +202 -0
- package/src/server/errors.ts +12 -0
- package/src/server/host.ts +74 -0
- package/src/server/inject.ts +13 -0
- package/src/server/runtime.ts +133 -0
- package/src/server/validate.ts +87 -0
- package/src/ui/NativeView.ts +142 -0
- package/src/ui/UI.ts +39 -0
- package/src/ui/UIBottomSheet.ts +139 -0
- package/src/ui/UIButton.ts +101 -0
- package/src/ui/UIContainer.ts +60 -0
- package/src/ui/UIImage.ts +83 -0
- package/src/ui/UIInput.ts +185 -0
- package/src/ui/UIModal.ts +139 -0
- package/src/ui/UINode.ts +830 -0
- package/src/ui/UIPager.ts +362 -0
- package/src/ui/UIPopover.ts +100 -0
- package/src/ui/UIScreen.ts +123 -0
- package/src/ui/UIScrollable.ts +87 -0
- package/src/ui/UISpacer.ts +14 -0
- package/src/ui/UITabs.ts +236 -0
- package/src/ui/UIText.ts +51 -0
- package/src/ui/UIVideo.ts +88 -0
- package/src/ui/UIVirtualizedList.ts +241 -0
- package/src/ui/UIWidget.ts +127 -0
- package/src/ui/fonts.ts +13 -0
- package/src/ui/presentable.ts +117 -0
- package/src/ui/router.ts +132 -0
- package/src/ui/theme.ts +84 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
## 3D scenes (non-AR)
|
|
2
|
+
|
|
3
|
+
// ===== SCENE =====
|
|
4
|
+
|
|
5
|
+
new Scene(options?: {
|
|
6
|
+
ibl?: boolean // image-based ambient light; default TRUE — every scene has ambient
|
|
7
|
+
environmentIntensity?: number // IBL strength; default 20000
|
|
8
|
+
bloom?: boolean // default false
|
|
9
|
+
bloomIntensity?: number // default 0.2 (only with bloom: true)
|
|
10
|
+
skybox?: ColorInput // solid background / clear color
|
|
11
|
+
antialias?: boolean // 4× MSAA; default false
|
|
12
|
+
})
|
|
13
|
+
// Options are constructor-only, except: scene.skybox = '#87ceeb' (setter) and scene.setAntialias(on, scale = 4).
|
|
14
|
+
|
|
15
|
+
scene.open(): void // become THE active scene (only one renders; closes any other)
|
|
16
|
+
scene.close(): void
|
|
17
|
+
Scene.active // static: active Scene | null
|
|
18
|
+
|
|
19
|
+
scene.createOverlay(options?): Scene // second scene drawn ON TOP, sharing this camera (3D HUD layer).
|
|
20
|
+
// Add nodes to it; it renders with its parent — never open() it.
|
|
21
|
+
await scene.warmRender() // render once off-screen so shaders compile — do it during a
|
|
22
|
+
// loading screen if the first visible frame hitches
|
|
23
|
+
|
|
24
|
+
// The camera is YOURS here: position it, lookAt targets, or drive it from an aspect (see FollowCam below).
|
|
25
|
+
|
|
26
|
+
// ===== CHARACTER CONTROLLER =====
|
|
27
|
+
// Kinematic collide-and-slide player (Jolt CharacterVirtual). Needs a Shape (capsule recommended),
|
|
28
|
+
// does NOT use Physics. Has its OWN gravity, separate from the world's. Updates in the EARLY
|
|
29
|
+
// phase — input set this frame is consumed by this frame's step.
|
|
30
|
+
node.aspect(Shape, { capsule: { halfHeight: 0.6, radius: 0.3 } })
|
|
31
|
+
.aspect(CharacterController, {
|
|
32
|
+
speed?: 5, // horizontal speed, units/s
|
|
33
|
+
jumpSpeed?: 7, // take-off speed
|
|
34
|
+
gravity?: -20, // character-only gravity
|
|
35
|
+
maxSlope?: 45, // degrees; attach-time only
|
|
36
|
+
})
|
|
37
|
+
node.controller.move(x, z) // horizontal intent, each in [-1, 1]; STICKY — send (0, 0) to stop
|
|
38
|
+
node.controller.jump() // queued; consumed next frame if grounded
|
|
39
|
+
node.controller.teleport(x, y, z) // clears vertical velocity
|
|
40
|
+
node.controller.grounded // boolean
|
|
41
|
+
node.controller.velocity // Vec3 (fresh copy)
|
|
42
|
+
// It passes through Triggers, and (v1) isn't detected by them and isn't pointer-pickable while active.
|
|
43
|
+
|
|
44
|
+
## 3D example
|
|
45
|
+
|
|
46
|
+
// Complete mini-game slice: physics scene, character on a ground plane, WASD + jump,
|
|
47
|
+
// click crates to knock them away, trigger goal zone, transparent UIText HUD.
|
|
48
|
+
|
|
49
|
+
<file name="main.ts">
|
|
50
|
+
Physics.configure({ gravity: [0, -9.81, 0] })
|
|
51
|
+
|
|
52
|
+
const scene = new Scene({ skybox: '#87ceeb', antialias: true })
|
|
53
|
+
scene.add(Light.sun({ direction: [1, -2, -1], shadowsQuality: 2 }))
|
|
54
|
+
|
|
55
|
+
// Ground: flat plane (normal up!) + static collider
|
|
56
|
+
scene.add(Mesh.plane({
|
|
57
|
+
material: Material.lit({ color: '#556655' }),
|
|
58
|
+
normal: [0, 1, 0], scale: 30, receiveShadows: true,
|
|
59
|
+
}).aspect(Shape, { box: [15, 0.05, 15] }).aspect(Physics, { motion: 'static' }))
|
|
60
|
+
|
|
61
|
+
// Crates: dynamic bodies, clickable via their Shape
|
|
62
|
+
let score = 0
|
|
63
|
+
for (let i = 0; i < 5; i++) {
|
|
64
|
+
const crate = Mesh.box({
|
|
65
|
+
material: Material.lit({ color: '#b5854b' }),
|
|
66
|
+
position: [i * 2 - 4, 0.5, -3], castShadows: true,
|
|
67
|
+
}).aspect(Shape, {}).aspect(Physics, { mass: 2 })
|
|
68
|
+
crate.addEventListener('click', () => {
|
|
69
|
+
crate.physics.applyImpulse([Mathf.random(-3, 3), 8, -6])
|
|
70
|
+
scoreLabel.text = `Score: ${++score}`
|
|
71
|
+
})
|
|
72
|
+
scene.add(crate)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Player: capsule character controller
|
|
76
|
+
const hero = Mesh.cylinder({
|
|
77
|
+
material: Material.lit({ color: '#3878f0' }),
|
|
78
|
+
position: [0, 1, 3], scale: [0.6, 1.8, 0.6], castShadows: true,
|
|
79
|
+
})
|
|
80
|
+
.aspect(Shape, { capsule: { halfHeight: 0.6, radius: 0.3 } })
|
|
81
|
+
.aspect(CharacterController, { speed: 6, jumpSpeed: 8 })
|
|
82
|
+
scene.add(hero)
|
|
83
|
+
|
|
84
|
+
// Goal zone: sensor that ends the round
|
|
85
|
+
const goal = Mesh.box({ material: Material.unlit({ color: '#44ff88' }), position: [0, 1, -8], scale: [2, 2, 0.3] })
|
|
86
|
+
.aspect(Shape, {})
|
|
87
|
+
.aspect(Trigger)
|
|
88
|
+
goal.addEventListener('enter', other => {
|
|
89
|
+
if (other === hero) scoreLabel.text = `You win! Score: ${score}`
|
|
90
|
+
})
|
|
91
|
+
scene.add(goal)
|
|
92
|
+
|
|
93
|
+
// Camera follows the hero (LATE default phase — reads final post-physics positions)
|
|
94
|
+
class FollowCam extends Aspect<'followCam', Node> {
|
|
95
|
+
update() {
|
|
96
|
+
const p = this.node.worldPosition
|
|
97
|
+
scene.camera.position = [p.x, p.y + 3, p.z + 6]
|
|
98
|
+
scene.camera.lookAt(p)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
hero.aspect(FollowCam)
|
|
102
|
+
|
|
103
|
+
setLoop(() => {
|
|
104
|
+
const x = (Input.key('KeyD') ? 1 : 0) - (Input.key('KeyA') ? 1 : 0)
|
|
105
|
+
const z = (Input.key('KeyS') ? 1 : 0) - (Input.key('KeyW') ? 1 : 0)
|
|
106
|
+
hero.controller.move(x, z)
|
|
107
|
+
if (Input.key('Space')) hero.controller.jump()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
// HUD over the 3D view: a UIWidget ATTACHED to the scene. (A UIScreen would REPLACE the scene —
|
|
111
|
+
// exactly one Presentable is visible at a time.)
|
|
112
|
+
let scoreLabel: UIText
|
|
113
|
+
const hud = UIWidget(
|
|
114
|
+
scoreLabel = UIText('Score: 0').style({ color: 'white', fontSize: 24, fontWeight: 700 }),
|
|
115
|
+
).style({ top: 'max(safe-top, 16px)', right: 16 })
|
|
116
|
+
|
|
117
|
+
scene.open()
|
|
118
|
+
hud.attachTo(scene).show()
|
|
119
|
+
</file>
|
package/prompts/3d.md
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
## 3D engine
|
|
2
|
+
|
|
3
|
+
Filament-rendered 3D content with Jolt physics. Units are world units ≈ METERS, Y is up, forward is −Z. All `eulerAngles` are DEGREES. Everything here is a global — no imports.
|
|
4
|
+
|
|
5
|
+
Scene construction and lifecycle are mode-specific — see the Scene (or AR) section further below. Everything in THIS section applies to any scene:
|
|
6
|
+
|
|
7
|
+
// ===== SCENE CONTENT (any scene kind) =====
|
|
8
|
+
|
|
9
|
+
scene.add(...nodes: Node[]): this // register with the draw set (≠ parenting — see hierarchy)
|
|
10
|
+
scene.remove(...nodes: Node[]): this
|
|
11
|
+
|
|
12
|
+
// Scene-level pointer events — fire for EVERY tap; ev.target = hit Node (needs a Shape) or null:
|
|
13
|
+
scene.addEventListener('click' | 'touchstart', (ev) => { ev.target; ev.clientX; ev.clientY })
|
|
14
|
+
|
|
15
|
+
// 2D HUD over a 3D view: open a UIScreen (no bgColor → transparent) AFTER the scene opens — see UI section.
|
|
16
|
+
|
|
17
|
+
// ===== NODES =====
|
|
18
|
+
// Node = empty transform. Mesh, Model, Light, Camera, Particles all extend it.
|
|
19
|
+
// The NATIVE engine owns the transform (physics rewrites it) — getters return fresh COPIES (see math).
|
|
20
|
+
|
|
21
|
+
const pivot = new Node()
|
|
22
|
+
pivot.position = [0, 1, -2] // Vec3Like (raw arrays fine); getters return Vec3 copies
|
|
23
|
+
pivot.x = 3 // scalar setters x / y / z — the way to move one axis
|
|
24
|
+
pivot.quaternion = Quat.fromEuler(0, 90, 0)
|
|
25
|
+
pivot.eulerAngles = [0, 90, 0] // DEGREES
|
|
26
|
+
pivot.scale = 2 // uniform number or [sx, sy, sz]
|
|
27
|
+
pivot.matrix = Mat4.compose([0, 1, 0], Quat.identity, 1)
|
|
28
|
+
|
|
29
|
+
node.worldMatrix // get/set full world transform
|
|
30
|
+
node.worldPosition / node.worldQuaternion / node.worldEulerAngles / node.worldScale // read-only
|
|
31
|
+
node.forward // Vec3: world-space −Z axis (read-only)
|
|
32
|
+
|
|
33
|
+
node.name = 'door' // visible/name; traverse lookups match on name
|
|
34
|
+
node.visible = false
|
|
35
|
+
node.destroy() // frees the native entity — the ONLY teardown; node unusable after
|
|
36
|
+
|
|
37
|
+
// Hierarchy (separate from scene membership — scene.add puts it in the world, node.add parents it):
|
|
38
|
+
node.add(...children): this
|
|
39
|
+
node.setParent(parent: Node | null, worldPositionStays = false): this
|
|
40
|
+
node.parent / node.children / node.childCount / node.getChild(i)
|
|
41
|
+
node.traverse(cb: (node: Node) => void) // this node, then all descendants
|
|
42
|
+
|
|
43
|
+
node.lookAt(point: Vec3Like, mode = '-z', up = [0, 1, 0]): this // aim a local axis at a WORLD point
|
|
44
|
+
|
|
45
|
+
// Node events — node.addEventListener(channel, cb) / removeEventListener:
|
|
46
|
+
// 'click' pointer-up over the node — REQUIRES a Shape aspect (pick body)
|
|
47
|
+
// 'touchstart' pointer-down over the node (ev.track() drags) — requires Shape
|
|
48
|
+
// 'enter' / 'exit' (other: Node) physics contact / trigger overlap began/ended — Shape + Physics/Trigger
|
|
49
|
+
// Without a Shape a node is INVISIBLE to taps — only scene listeners fire (ev.target === null).
|
|
50
|
+
|
|
51
|
+
// ===== ASPECTS =====
|
|
52
|
+
// Capabilities attach to nodes as aspects and chain; each adds a named accessor:
|
|
53
|
+
node.aspect(Class, opts?) // attach + configure; returns the node (typed with the accessor)
|
|
54
|
+
node.get(Class) / node.has(Class) / node.removeAspect(Class)
|
|
55
|
+
// Named accessors: node.physics, node.trigger, node.controller, node.shape, node.anim
|
|
56
|
+
// Custom game logic = your own aspect with per-frame update(dt seconds):
|
|
57
|
+
class Spin extends Aspect<'spin', Node> { // <accessor name, node kind>
|
|
58
|
+
speed = 90 // class fields = configurable defaults (opts override)
|
|
59
|
+
update(dt: number) { this.node.eulerAngles = this.node.eulerAngles.add([0, this.speed * dt, 0]) }
|
|
60
|
+
}
|
|
61
|
+
mesh.aspect(Spin, { speed: 45 }) // never `new Spin()`; init in onAttach(), clean in onDetach()
|
|
62
|
+
// Phases: default = LATE (after physics — for cameras/followers reading final positions);
|
|
63
|
+
// set `updateBeforePhysics = true` on aspects that WRITE velocity/kinematic transforms.
|
|
64
|
+
|
|
65
|
+
// ===== MESHES & MATERIALS =====
|
|
66
|
+
|
|
67
|
+
// Primitive factories — all take: { material? (default Material.unlit()), position?, eulerAngles?,
|
|
68
|
+
// scale?, name?, castShadows?, receiveShadows? }. Defaults are UNIT-sized (1 m).
|
|
69
|
+
Mesh.box({ size?: Vec3Like | number }) // full edge lengths; default 1×1×1
|
|
70
|
+
Mesh.sphere({ radius? }) // default 0.5 (unit diameter)
|
|
71
|
+
Mesh.cylinder({ radius?, radiusTop?, radiusBottom?, edges? }) // height fixed 1; radiusTop: 0 = cone
|
|
72
|
+
Mesh.plane({ normal?: Vec3Like }) // 1×1 quad; default normal [0,0,1] = STANDS UPRIGHT facing camera.
|
|
73
|
+
// A ground/floor plane NEEDS normal: [0, 1, 0].
|
|
74
|
+
|
|
75
|
+
mesh.material // get/set slot 0; mesh.setMaterial(mat, index = 0)
|
|
76
|
+
mesh.castShadows = true // write-only render flags (or set in factory options)
|
|
77
|
+
mesh.receiveShadows = true
|
|
78
|
+
|
|
79
|
+
// Materials:
|
|
80
|
+
Material.lit({ color?, map?: Texture }) // PBR — needs light (IBL and/or sun) or renders black
|
|
81
|
+
Material.unlit({ color?, map? }) // flat, ignores all lighting
|
|
82
|
+
Material.video(player.texture) // samples a VideoPlayer's texture
|
|
83
|
+
Material.shadow(color = '#000000aa') // shadow-catcher: invisible except where shadows fall (AR staple)
|
|
84
|
+
material.color = '#ff8800' // convenience setters on lit/unlit (no-op on custom shaders)
|
|
85
|
+
material.map = tex // Texture | Canvas | null
|
|
86
|
+
|
|
87
|
+
// Custom compiled shaders (.mat files) — load, then set uniforms by name (chainable):
|
|
88
|
+
const mat = new Material(await fetch(asset('./hologram.mat'), { useOnce: true }))
|
|
89
|
+
.set('baseColor', '#ffa200') // '#rrggbb' / '#rrggbbaa' hex STRINGS only
|
|
90
|
+
.set('roughness', 0.54) // number → float, boolean → bool
|
|
91
|
+
.set('glowMap', tex) // Texture → sampler
|
|
92
|
+
.set('dir', [0, 1, 0]) // number[] / Float32Array → vector. NOT a Vec3 — spread it: [...v]
|
|
93
|
+
// material.uniforms.key = value — same thing property-style. `color`/`map` setters don't know a
|
|
94
|
+
// custom shader's uniform names — always .set() the real names there.
|
|
95
|
+
|
|
96
|
+
// Textures:
|
|
97
|
+
const tex = await Texture.load(asset('./crate.jpg')) // url string | FetchResponse | File
|
|
98
|
+
tex.wrapS = 1; tex.wrapT = 1 // set BEFORE assigning to a material
|
|
99
|
+
Texture.fromCanvas(c) // bake a Canvas (see CANVAS section) — nameplates, generated art;
|
|
100
|
+
// material.map = c does the same implicitly; c.update() refreshes it
|
|
101
|
+
// No dispose() — textures live until the engine tears down. Load once, reuse everywhere.
|
|
102
|
+
|
|
103
|
+
// Light — Light.sun() is the ONLY light (no point/spot lights). Ambient comes from the scene's IBL.
|
|
104
|
+
scene.add(Light.sun({
|
|
105
|
+
direction?: Vec3Like, // where light TRAVELS; default a key-light angle
|
|
106
|
+
intensity?: number, // default 100000
|
|
107
|
+
color?: ColorInput,
|
|
108
|
+
shadowsQuality?: 0 | 1 | 2 | 3 // 0 = none, 1 = default 1024 map, 2 = soft 2048, 3 = PCSS 4096
|
|
109
|
+
}))
|
|
110
|
+
// Creation-time only — no setters; to change the sun, destroy() it and add a new one.
|
|
111
|
+
// Shadows also need castShadows on the caster + receiveShadows on the ground.
|
|
112
|
+
|
|
113
|
+
// Custom geometry — tweak a primitive before wrapping (moves pivot, tiles UVs), then Mesh.from(geo):
|
|
114
|
+
const geo = Geometry.cylinder({ radiusTop: 0, edges: 6 }).scale(1, 3, 1).translate(0, 1.5, 0)
|
|
115
|
+
const spike = Mesh.from(geo, { material }) // finish geometry BEFORE building the mesh (buffers upload once)
|
|
116
|
+
|
|
117
|
+
// ===== MODELS (GLB) =====
|
|
118
|
+
|
|
119
|
+
const hero = await Model.load(asset('./hero.glb')) // asset path | https URL | FetchResponse; rejects on error
|
|
120
|
+
scene.add(hero) // a Model IS a Node — transform/events/aspects all apply
|
|
121
|
+
hero.traverse(n => { if (n.name === 'Sword') n.visible = false }) // GLB internals are plain child Nodes
|
|
122
|
+
|
|
123
|
+
// Animation — every Model has an Animator pre-attached at model.anim (clip list = the glb's clips). A layer
|
|
124
|
+
// has a LOOP (what it rests on) and ONE-SHOTS played over it; any call overrides what's there over its own fade:
|
|
125
|
+
hero.anim.clips / hero.anim.clip('Run') / hero.anim.clip(0) // AnimationClip[] (file order) / by name / by index; unnamed clips = 'animation_0', …
|
|
126
|
+
hero.anim.playLoop('Idle', { fade: 0.2 }) // rest on a looping clip (crossfades from whatever plays); hero.anim.loop reads it
|
|
127
|
+
const loco = hero.anim.playLoop({ Idle: 0, Walking: 2, 'Fast Run': 6 }) // a BLEND as the loop (1D; [x, y] positions = 2D)
|
|
128
|
+
loco.value = speed // position on the axis — members stay in phase (no foot sliding)
|
|
129
|
+
const ok = await hero.anim.play('slash', { fadeIn: 0.1, fadeOut: 0.3 }) // one-shot over the loop, returns to it
|
|
130
|
+
// fades default to 0 (cut): fade = both ends, fadeIn / fadeOut = one end. Unknown name warns, plays nothing.
|
|
131
|
+
// The await resolves at the HAND-OVER (end − fadeOut) with true (false = cut short) — what you start right after is what
|
|
132
|
+
// the clip fades into: chains crossfade: if (ok) await hero.anim.play('slash2', { fadeIn: 0.3, fadeOut: 0.3 })
|
|
133
|
+
// then hero.anim.playLoop('Crouch', { fade: 0.3 }) — or nothing = back to the current loop.
|
|
134
|
+
if (!hero.anim.busy) hero.anim.play('kick') // busy = a one-shot hasn't handed over; play() always takes over otherwise
|
|
135
|
+
hero.anim.stop({ fade: 0.2 }) // everything off → rest; hero.anim.busy; hero.anim.speed = 0.3 (0 = pause)
|
|
136
|
+
// p = play(): p.done (return THIS from async fns) / p.playing / p.progress / p.weight / p.stop({ fade }) / p.seek(t)
|
|
137
|
+
// Clips from other files (Mixamo: one GLB per animation), procedural, sliced — add by name:
|
|
138
|
+
const [idle, slash] = await Promise.all([AnimationClip.load(asset('./idle.glb')), AnimationClip.load(asset('./attacks.glb'), 'Slash')])
|
|
139
|
+
hero.anim.addClip('slash', slash).addClip('kick', hero.anim.clip('Kick').slice(0.2, 1.1)) // slice = a sub-range, re-timed
|
|
140
|
+
AnimationClip.from({ tracks: { Hips: { position: [[0, [0,0,0]], [1, [0,0.05,0]]] } } }) // curves in code, binds by node NAME
|
|
141
|
+
// Clip events live on the CLIP, in SECONDS: slash.addEvent(0.4, 'hit') → hero.anim.on('hit', (clip, layer) => dealDamage())
|
|
142
|
+
// Layers (legs walk, arms aim): const upper = hero.anim.addLayer({ mask: 'Spine1' }); upper.playLoop('Aim', { fade: 0.3 }); upper.stop()
|
|
143
|
+
// { additive: true } = each clip's DELTA vs its first frame on top (recoil, breathe); upper.weight = 0.5
|
|
144
|
+
// Root motion (clips whose hips travel — Mixamo without 'In Place', rolls): hero.anim.rootMotion = true → the node moves
|
|
145
|
+
// (or its CharacterController, as a velocity — it collides); the bone stays put
|
|
146
|
+
hero.bone('RightHand')?.add(sword) // bones are Nodes — sockets
|
|
147
|
+
// FBX assets (Mixamo exports FBX): `lecodes assets convert hero.fbx --clips Idle.fbx Run.fbx -o hero.glb` → one GLB, clips merged by bone name
|
|
148
|
+
// IK — late-phase aspects ON BONES (over the Animator's pose): TwoBone on the END bone, LookAt on the bone itself
|
|
149
|
+
hero.bone('LeftFoot').aspect(IK.TwoBone, { target: footPoint, pole: kneeHint }).ik.weight = grounded ? 1 : 0
|
|
150
|
+
hero.bone('Head').aspect(IK.LookAt, { target: camera, limit: 70 }) // axis = bone-local forward, default [0,0,-1]
|
|
151
|
+
|
|
152
|
+
// ===== PHYSICS (Jolt) =====
|
|
153
|
+
|
|
154
|
+
Physics.supported // static boolean — without it EVERYTHING here silently no-ops
|
|
155
|
+
// (aspects attach fine, velocity reads [0,0,0], no events) — gate gameplay
|
|
156
|
+
Physics.configure({ gravity?: [0, -9.81, 0], maxBodies?: 4096 }) // ONCE, BEFORE any body exists
|
|
157
|
+
Physics.interpolation = true // static; default on — smooths bodies between fixed 60 Hz steps
|
|
158
|
+
|
|
159
|
+
// Shape — collision geometry AND pickability. MUST be attached BEFORE Physics/Trigger/CharacterController
|
|
160
|
+
// (they throw "requires a Shape aspect" otherwise). Pick exactly one kind:
|
|
161
|
+
node.aspect(Shape, {
|
|
162
|
+
box?: Vec3Like, // HALF-extents, WORLD units, NOT scaled by the node
|
|
163
|
+
sphere?: number, // radius
|
|
164
|
+
cylinder?: { halfHeight: number, radius: number }, // Y-aligned
|
|
165
|
+
capsule?: { halfHeight: number, radius: number }, // halfHeight = cylinder section only
|
|
166
|
+
mesh?: true | 'convex', // the node's OWN triangles (Mesh geometry or Model GLB, × world scale)
|
|
167
|
+
origin?: Vec3Like, // collider centre, offset from the node's pivot (world units, node rotation, NOT scaled)
|
|
168
|
+
raycast?: boolean, // default true; false = invisible to taps & raycasts
|
|
169
|
+
})
|
|
170
|
+
// mesh: true = exact triangle mesh → level geometry / terrain / ramps: static or kinematic Physics, Trigger,
|
|
171
|
+
// pick bodies, character ground. NEVER dynamic (Physics throws). mesh: 'convex' = convex hull → dynamic props.
|
|
172
|
+
// A Model collider is its bind pose (skinned parts skipped) — animated characters keep a capsule.
|
|
173
|
+
node.aspect(Shape, {}) // auto: box from the mesh AABB × world scale — right for most meshes
|
|
174
|
+
// origin: art modelled ABOVE its pivot (a character on its feet, a barrel on its base) needs the
|
|
175
|
+
// collider lifted: { capsule: {…}, origin: [0, 0.9, 0] }. An auto shape already centres on its mesh.
|
|
176
|
+
node.shape.fit(kind?) // measure dimensions + origin from what the node RENDERS (subtree,
|
|
177
|
+
// so a Model root works where {} can't) and rebuild the live collider. Fit AFTER Model.load resolves.
|
|
178
|
+
// A bare Shape (no Physics) creates a STATIC pick-only body → the node gets 'click'/'touchstart'.
|
|
179
|
+
// It snapshots the transform at attach time — a MOVING pickable object needs a Physics body too.
|
|
180
|
+
|
|
181
|
+
// Physics — rigid body:
|
|
182
|
+
node.aspect(Shape, {}).aspect(Physics, {
|
|
183
|
+
motion?: 'static' | 'dynamic' | 'kinematic', // default 'dynamic'
|
|
184
|
+
mass?: number, // kg, dynamic only; default 1
|
|
185
|
+
})
|
|
186
|
+
node.physics.velocity // Vec3 world units/s — get (fresh copy) / set
|
|
187
|
+
node.physics.angularVelocity // Vec3 DEGREES/s about each world axis — get / set
|
|
188
|
+
node.physics.applyImpulse(v): this // instant impulse, wakes the body
|
|
189
|
+
node.position = [x, y, z] // place / teleport — moves the node AND its body (no moveTo())
|
|
190
|
+
node.eulerAngles = [0, 90, 0] // orientation is routed to the body too (NOT for a CharacterController)
|
|
191
|
+
// A teleport keeps BOTH velocities: putting an object down is velocity = 0, angularVelocity = 0, position = p
|
|
192
|
+
// PHYSICS OWNS a dynamic body's transform: a position write teleports, but the sim takes over again —
|
|
193
|
+
// drive motion with velocity / applyImpulse. READING node.position/worldPosition is always correct.
|
|
194
|
+
// static = never moves (floors, walls); kinematic = write position each frame, pushes but isn't pushed.
|
|
195
|
+
|
|
196
|
+
// Contact events — on BOTH nodes of a contact/overlap, arg = the other node:
|
|
197
|
+
crate.addEventListener('enter', (other: Node) => {})
|
|
198
|
+
crate.addEventListener('exit', (other: Node) => {})
|
|
199
|
+
|
|
200
|
+
// Trigger — static sensor zone (needs a Shape; no options). Overlapping bodies fire 'enter'/'exit'
|
|
201
|
+
// instead of colliding; never blocks movement:
|
|
202
|
+
const goal = Mesh.box({ position: [0, 1, -6] }).aspect(Shape, { box: [1, 1, 0.2] }).aspect(Trigger)
|
|
203
|
+
goal.addEventListener('enter', other => win(other))
|
|
204
|
+
goal.trigger.moveTo(p)
|
|
205
|
+
|
|
206
|
+
// Raycast — closest PICKABLE body (any Shape with raycast: true, incl. pick-only & triggers):
|
|
207
|
+
Physics.raycast(origin: Vec3Like, dir: Vec3Like, maxDist = 1000)
|
|
208
|
+
// → { node: Node | null, point: Vec3, normal: Vec3, fraction: number /* 0..1 of maxDist×|dir| */ } | null
|
|
209
|
+
|
|
210
|
+
// ===== PARTICLES =====
|
|
211
|
+
// GPU particle emitter node — move/rotate the node to move the emitter. Every option is a live setter.
|
|
212
|
+
new Particles({
|
|
213
|
+
material: Material, // REQUIRED — Material.unlit() is the usual choice
|
|
214
|
+
rate?: number, // particles/second (plain number only — a range is ignored)
|
|
215
|
+
shape?: { type: 'point', v: Vec3Like } | { type: 'box', min: Vec3Like, max: Vec3Like },
|
|
216
|
+
startVelocity?: Vec3Like | { // fixed vector, or a direction mode:
|
|
217
|
+
dir?: Vec3Like, from?: Vec3Like, to?: Vec3Like, // along / away-from / toward
|
|
218
|
+
speed?: number | { min, max },
|
|
219
|
+
randomizeAngle?: { min: Vec3Like, max: Vec3Like }, // jitter, degrees per axis
|
|
220
|
+
},
|
|
221
|
+
gravity?: number, drag?: number,
|
|
222
|
+
lifetime?: number | { min, max }, // seconds; range = random per particle
|
|
223
|
+
color?: string | { min, max } | DynamicColor,
|
|
224
|
+
size?: number | { min, max } | DynamicValue,
|
|
225
|
+
rotation?: number | { min, max } | DynamicValue,
|
|
226
|
+
noise?: { strength?, frequency?, speed? } | null, // turbulence
|
|
227
|
+
})
|
|
228
|
+
sparks.spawn(count) // burst on top of rate; one-shot effect = rate: 0 + spawn(n)
|
|
229
|
+
// Curves over each particle's life (t runs 0..1): flat (t, value, t, value, …) stop lists:
|
|
230
|
+
sparks.size = dynamic(0.1, 'multiply', 0, 1, 1, 4) // grow to 4× by death
|
|
231
|
+
sparks.color = dynamicColor('#ffaa33', 0.7, '#ffffff', 1, '#000000') // multiplies base — fade to black
|
|
232
|
+
sparks.destroy() // from Node — removes the system
|
|
233
|
+
|
|
234
|
+
// ===== CAMERA / RAY / PLANE / NOISE =====
|
|
235
|
+
|
|
236
|
+
scene.camera // a Node — never constructed; every scene owns one.
|
|
237
|
+
// (Who MOVES it is mode-specific — see the Scene/AR section.)
|
|
238
|
+
camera.fov / .near / .far // lens: vertical FOV degrees (60), clip 0.01 / 1000 — SETTABLE.
|
|
239
|
+
camera.setProjection({ fov, near, far }) // any subset; .horizontalFov, .displaySize stay read-only
|
|
240
|
+
camera.getViewDirection(screenX, screenY): Vec3 // world dir through a screen point (logical px = ev.clientX/Y)
|
|
241
|
+
camera.getRay(screenX, screenY): Ray // origin = camera.worldPosition + that direction
|
|
242
|
+
|
|
243
|
+
new Ray(origin, dir) // .origin, .dir, .getPoint(t) → origin + t·dir
|
|
244
|
+
new Plane(normal, point) // infinite math plane (NOT a physics body)
|
|
245
|
+
plane.intersectRay(ray): number | null // returns the PARAMETER t, not the point → ray.getPoint(t)
|
|
246
|
+
plane.signedDistance(p) / plane.projectPoint(p)
|
|
247
|
+
|
|
248
|
+
// Drag-on-ground recipe — tap/drag to a world point on the floor:
|
|
249
|
+
const ground = new Plane([0, 1, 0], [0, 0, 0])
|
|
250
|
+
scene.addEventListener('click', ev => {
|
|
251
|
+
const ray = scene.camera.getRay(ev.clientX, ev.clientY)
|
|
252
|
+
const t = ground.intersectRay(ray)
|
|
253
|
+
if (t !== null) marker.position = ray.getPoint(t)
|
|
254
|
+
})
|
|
255
|
+
// Against real bodies instead: Physics.raycast(ray.origin, ray.dir, 100)
|
|
256
|
+
|
|
257
|
+
const noise = new Noise() // native Perlin/fractal; deterministic — animate by moving the sample point
|
|
258
|
+
noise.get(x, y) / noise.get3d(x, y, z)
|
|
259
|
+
noise.frequency = 0.01; noise.octaves = 1 // + fractalLacunarity, fractalGain
|
|
260
|
+
|
|
261
|
+
// ===== COMMON MISTAKES — DO NOT DO THESE =====
|
|
262
|
+
|
|
263
|
+
// ❌ wrong accessor name
|
|
264
|
+
node.body.applyImpulse(...) // WRONG — the accessor is node.physics
|
|
265
|
+
// ✅ node.physics / node.trigger / node.controller / model.anim
|
|
266
|
+
|
|
267
|
+
// ❌ writing a dynamic body's transform per frame — physics owns it and fights back
|
|
268
|
+
setLoop(() => { crate.position = target })
|
|
269
|
+
// ✅ drive the simulation: crate.physics.velocity = ... / applyImpulse(...); kinematic → moveTo(...)
|
|
270
|
+
|
|
271
|
+
// ❌ Physics/Trigger/CharacterController before Shape — throws
|
|
272
|
+
node.aspect(Physics).aspect(Shape, {})
|
|
273
|
+
// ✅ Shape FIRST
|
|
274
|
+
node.aspect(Shape, {}).aspect(Physics)
|
|
275
|
+
|
|
276
|
+
// ❌ full extents in Shape box — collider twice the mesh
|
|
277
|
+
Mesh.box().aspect(Shape, { box: [1, 1, 1] }) // 2×2×2 collider around a 1×1×1 box
|
|
278
|
+
// ✅ HALF-extents, or {} to auto-fit
|
|
279
|
+
Mesh.box().aspect(Shape, { box: [0.5, 0.5, 0.5] })
|
|
280
|
+
|
|
281
|
+
// ❌ cloning a Model
|
|
282
|
+
const copy = model.clone() // doesn't exist
|
|
283
|
+
// ✅ pool: Promise.all(Array.from({ length: N }, () => Model.load(url))) up front, recycle
|
|
284
|
+
|
|
285
|
+
// ❌ a ground plane with the default normal — it stands upright facing the camera
|
|
286
|
+
Mesh.plane({ scale: 20 })
|
|
287
|
+
// ✅ Mesh.plane({ scale: 20, normal: [0, 1, 0] })
|
|
288
|
+
|
|
289
|
+
// ❌ Vec3/Quat as a custom-shader uniform value — lowers to NULL
|
|
290
|
+
mat.set('lightDir', dir)
|
|
291
|
+
// ✅ spread to a plain array: mat.set('lightDir', [...dir])
|
|
292
|
+
|
|
293
|
+
// ❌ point/spot lights — they don't exist
|
|
294
|
+
Light.point(...)
|
|
295
|
+
// ✅ Light.sun() + scene IBL is the whole lighting model; fake glows with unlit/bloom/particles
|
|
296
|
+
|
|
297
|
+
// ❌ rgba()/named colors in 3D APIs — silently black
|
|
298
|
+
Material.lit({ color: 'rgba(255, 0, 0, 0.5)' })
|
|
299
|
+
// ✅ hex string or packed int: '#ff0000' / '#ff000080' / 0xff0000
|
|
300
|
+
|
|
301
|
+
// ❌ a fresh Material per frame (or per particle) to animate a look
|
|
302
|
+
setLoop(() => { mesh.material = Material.lit({ color: next() }) })
|
|
303
|
+
// ✅ mutate the one material: mesh.material.color = next() / mat.set('progress', t)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Codegen prompts
|
|
2
|
+
|
|
3
|
+
> **Scope: the le.codes PLATFORM assistant only.** These prompts are bundled into `dist/` and served
|
|
4
|
+
> by claude-proxy to the in-editor chat. They are NOT used by local development — Claude Code and
|
|
5
|
+
> `lecodes-cli` never read them; those get the SDK surface from `.lecodes/types` (the d.ts bundle),
|
|
6
|
+
> the generated project `CLAUDE.md` (`lecodes-cli/src/commands/projectTemplates.ts`) and `sdk/docs`.
|
|
7
|
+
> A change meant for local development belongs there, not here.
|
|
8
|
+
|
|
9
|
+
The system prompt(s) sent to Claude for LeCodes code generation, split into composable modules so
|
|
10
|
+
a request only pays for the API areas it can actually use. Facts come from `../docs/` (the source
|
|
11
|
+
of truth) — when the SDK changes, update docs first, then mirror the change here.
|
|
12
|
+
|
|
13
|
+
**Consumers:**
|
|
14
|
+
- `claude-proxy/prompts/*.md` — deployed copies of `dist/` + `router.md` (see its README); the
|
|
15
|
+
proxy injects them per the `x-lecodes-bundle` header (`claude-proxy/src/proxy/bundles.ts`). An
|
|
16
|
+
unknown/missing header is a **400** (no silent fallback), and the proxy's `/health` exposes a
|
|
17
|
+
sha256-12 per bundle — `bun run check:prompts` (repo root) compares them against `dist/` to
|
|
18
|
+
catch deploy drift (`--dir <proxy>/prompts` checks a local checkout instead).
|
|
19
|
+
- `packages/frontend/.../chat/engine/bundleSelect.ts` — a thin adapter over `select.ts` (imported
|
|
20
|
+
as `sdk/prompts/select`); the selection logic itself is not duplicated anywhere.
|
|
21
|
+
|
|
22
|
+
## Layout
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
core.md shared foundation: response format, entry-point rule, runtime, math, tweens
|
|
26
|
+
index.md one-line map of EVERY global — always included (anti-hallucination anchor)
|
|
27
|
+
canvas.md Canvas drawing surface — engine-independent, in every bundle (each engine
|
|
28
|
+
module documents its own hookup: Sprite texture / UIImage src / Texture)
|
|
29
|
+
ui.md UI module (screens, router, styling, widgets, lists)
|
|
30
|
+
2d.md 2D engine module (sprites, tilemaps, Box2D physics, aspects, Canvas)
|
|
31
|
+
3d.md SHARED 3D content (nodes, meshes, models, materials, physics, particles)
|
|
32
|
+
3d-scene.md non-AR scenes: Scene construction, camera control, CharacterController, example
|
|
33
|
+
3d-scene-files.md declarative .scene.ts files (defineScene/use/ref/make, SceneHandle, HUD-over-scene)
|
|
34
|
+
— the visual scene editor's format; non-AR only (camera nodes, skybox env)
|
|
35
|
+
ar.md AR scenes: ARScene, anchors, placement controls, example — never with 3d-scene.md
|
|
36
|
+
core-design.md design mode's core: directives/context/reports, no runtime APIs (no timers/network)
|
|
37
|
+
ui-design.md design mode's UI: curated static-mockup subset of ui.md (KEEP IN SYNC with it)
|
|
38
|
+
design.md design mode: canvas conventions — kit-first, states, design_map tool, tab bar
|
|
39
|
+
concept.md concept mode: product-shaping conversation, writes ONLY design/spec.md
|
|
40
|
+
compose.ts builds dist/<bundle>.md from the modules
|
|
41
|
+
select.ts deterministic bundle detection from project code
|
|
42
|
+
router-prompt.md fallback classifier prompt (empty projects only)
|
|
43
|
+
namer-prompt.md project name + icon-emoji generator (first request of a new project)
|
|
44
|
+
intake-prompt.md new-project intake: clarifying questions + confirmable brief (two Haiku turns)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Bundles (`bun run prompts/compose.ts` → `dist/`)
|
|
48
|
+
|
|
49
|
+
| Bundle | Modules | For |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `ui-app` | core + index + canvas + ui | apps: screens, forms, lists, chat, commerce |
|
|
52
|
+
| `2d-game` | core + index + canvas + 2d + ui | flat games, drawing toys |
|
|
53
|
+
| `3d-app` | core + index + canvas + 3d + 3d-scene + 3d-scene-files + ui | 3D games/viewers, scene-editor projects |
|
|
54
|
+
| `ar-app` | core + index + canvas + 3d + ar + ui | AR experiences |
|
|
55
|
+
| `design` | core-design + ui-design + design | design mode: the design/ prototyping canvas |
|
|
56
|
+
| `concept` | concept | concept mode: shape the idea, maintain design/spec.md (~0.8k tokens) |
|
|
57
|
+
|
|
58
|
+
Fixed bundles (not per-request assembly) are deliberate: **each bundle is a stable prompt prefix**,
|
|
59
|
+
so Anthropic prompt caching hits on every generation after the first (~10× cheaper input, lower
|
|
60
|
+
latency). Per-request cherry-picking would produce near-unique prefixes and always pay full price.
|
|
61
|
+
Game bundles include the full UI module because games need HUDs/menus; the ui-app bundle omits the
|
|
62
|
+
engines entirely.
|
|
63
|
+
|
|
64
|
+
## Selecting the bundle
|
|
65
|
+
|
|
66
|
+
`design` and `concept` are different from the engine bundles: they are selected EXPLICITLY by the
|
|
67
|
+
client from the editor's mode switcher (Concept / Design / Build — `x-lecodes-bundle: design` or
|
|
68
|
+
`concept`) — never routed, detected from code, or reachable via the `<bundle>` escalation. The
|
|
69
|
+
engine ladder below applies to Build mode only. Concept turns also carry no code context (design
|
|
70
|
+
excerpts + file names only) and the client enforces each mode's write boundary (concept:
|
|
71
|
+
design/spec.md only; design: design/ only). Any bundle may end a reply with `<mode>x</mode>` — the
|
|
72
|
+
client strips it and renders a "Switch to …" button; the mode never switches itself.
|
|
73
|
+
|
|
74
|
+
For the engine bundles — three signals, in this order:
|
|
75
|
+
|
|
76
|
+
**1. Project code — `select.ts` (primary, deterministic).**
|
|
77
|
+
`detectBundle(files)` scans the project's `.ts` sources for engine globals (comments and string
|
|
78
|
+
literals stripped) and returns the bundle. This is language-independent — it doesn't matter what
|
|
79
|
+
language the user writes in, because it never reads the user's words at all. It covers every
|
|
80
|
+
request against an existing project, i.e. the vast majority of traffic. Priority: AR > 3D > 2D;
|
|
81
|
+
sources with no engine references → `ui-app`. This mirrors the engine-kind detection the compiler
|
|
82
|
+
already does for project icons — if that detection is exposed server-side, reuse it instead.
|
|
83
|
+
|
|
84
|
+
**0. Intake — `intake-prompt.md` (new projects, preferred over the router).**
|
|
85
|
+
The session's first request against a project with no app code runs the intake instead of building:
|
|
86
|
+
one Haiku call returns 2–4 clarifying questions with tappable options (including the app kind when
|
|
87
|
+
the request is ambiguous between kinds), a second call turns the answers into a short brief the
|
|
88
|
+
user confirms. The confirmed kind sets the first build turn's bundle explicitly (`x-lecodes-bundle`
|
|
89
|
+
sent by the client — the router below never runs), and the brief seeds `design/spec.md` as the
|
|
90
|
+
project concept. Intake failure or the user's Skip degrades to the flow below — it never blocks.
|
|
91
|
+
Client side: `frontend/.../chat/engine/intake.ts` + the intake card in `chatController.ts`.
|
|
92
|
+
|
|
93
|
+
**2. Router model — `router-prompt.md` (fallback: intake skipped/failed on empty projects).**
|
|
94
|
+
When `detectBundle` returns `null` (no code yet), classify the user's first message with one cheap
|
|
95
|
+
call: `claude-haiku-4-5`, temperature 0, `max_tokens: 8`, reply is a single word `ui|2d|3d|ar`.
|
|
96
|
+
The prompt is meaning-based and multilingual (EN/RU examples included). Parse defensively; any
|
|
97
|
+
garbage → `ui`. Cost/latency: ~1k input tokens, a few output tokens, ~200–400 ms — negligible, and
|
|
98
|
+
it runs at most once per project's life.
|
|
99
|
+
|
|
100
|
+
**Stickiness.** Cache the chosen bundle per conversation and only move UP
|
|
101
|
+
(`upgradeBundle(current, detected)`: ui → 2d/3d → ar), re-running `detectBundle` over files the
|
|
102
|
+
assistant itself just generated. Never downgrade mid-conversation — it would invalidate the prompt
|
|
103
|
+
cache and confuse in-flight context. Wrong-but-bigger is harmless; wrong-but-smaller causes
|
|
104
|
+
hallucinated APIs.
|
|
105
|
+
|
|
106
|
+
**3. Escalation — the `<bundle>` directive (recovery from a misroute).**
|
|
107
|
+
Code-based detection can never recover from a wrong-but-smaller bundle on its own: a model that
|
|
108
|
+
wasn't shown an engine never emits its globals, so the upgrade never triggers. core.md ("Missing
|
|
109
|
+
engine") therefore instructs the generator to reply with ONLY `<bundle>ar</bundle>` (or `3d`/`2d`)
|
|
110
|
+
when the request needs engine APIs its prompt doesn't document. The client (`parseBundleDirective`
|
|
111
|
+
in `select.ts`) discards that reply, upgrades the pinned bundle, and re-sends the same request
|
|
112
|
+
once — upgrade-only here too; a non-upgradable directive (e.g. a 3D project asking for the 2D
|
|
113
|
+
engine) surfaces as a plain explanation instead. Relatedly, the router's classifier input must be
|
|
114
|
+
ONLY the user's words: the proxy cuts everything before the frontend's `[Request]` marker, so e.g.
|
|
115
|
+
a `.glb` in the project snapshot can't drag an explicit AR request toward `3d`.
|
|
116
|
+
|
|
117
|
+
## Naming new projects
|
|
118
|
+
|
|
119
|
+
`namer-prompt.md` is unrelated to bundle selection but lives here with the other model prompts:
|
|
120
|
+
an assistant request made while the project has NO CODE yet (no non-empty `.ts` file — uploaded
|
|
121
|
+
assets like a `.glb` model don't count as "started") also fires one extra Haiku call in parallel
|
|
122
|
+
(`x-lecodes-bundle: namer`, `chat/engine/projectNamer.ts`) that returns bare JSON `{name, emoji}`;
|
|
123
|
+
the result is saved via the normal project-update API (name + `iconEmoji`, drawn by
|
|
124
|
+
`VProjectIcon`). Uncached — the prompt is far below Haiku's minimum cacheable prefix. Parsing is
|
|
125
|
+
defensive: a garbled reply must never rename the project, so it's rejected rather than truncated
|
|
126
|
+
into place; a rename while the call is in flight also wins over the generated name.
|
|
127
|
+
|
|
128
|
+
**Why not route every request with a model?** It adds a failure mode (misroute → the generator
|
|
129
|
+
confidently invents APIs it wasn't shown), latency, and cost on 100% of traffic to solve a problem
|
|
130
|
+
that exists only for the ~one request per project where no code exists. Residual misroutes are
|
|
131
|
+
recoverable: the index.md map means the generator always knows what exists, and the `<bundle>`
|
|
132
|
+
escalation directive (above) lets it swap in the right engine module instead of guessing.
|
|
133
|
+
|
|
134
|
+
## Keeping prompts honest
|
|
135
|
+
|
|
136
|
+
- `index.md` must list every global exported from `sdk/src/inject.ts` — same contract as
|
|
137
|
+
`docs/check.mjs`. When a global is added, update docs, then the module, then index.md.
|
|
138
|
+
- Known corrections baked into these prompts (do not regress; see `docs/AUTHORING.md`):
|
|
139
|
+
`onEndReached(thresholdPx, cb)` (threshold first), `bgSize: cover|contain|tile` (no `fill`),
|
|
140
|
+
track `deltaX/deltaY` are per-move, engine colors are hex/packed-int only, `animate` can't
|
|
141
|
+
tween strings/colors, `node.physics` (not `.body`), no `Camera2D.follow`, no
|
|
142
|
+
`UIScrollable.scrollTo`, models can't be cloned.
|