lecodes-sdk 0.20.0 → 1.0.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/dist/global.d.ts +48 -5
- package/dist/inject.js +361 -260
- package/dist/types/audio/Bus.d.ts +45 -0
- package/dist/types/audio/Sound.d.ts +28 -0
- package/dist/types/audio/Voice.d.ts +27 -0
- package/dist/types/audio/audio.d.ts +83 -0
- package/dist/types/audio/support.d.ts +1 -0
- package/dist/types/canvas/Canvas.d.ts +2 -0
- package/dist/types/gl/DecalSet.d.ts +148 -0
- package/dist/types/gl/Geometry.d.ts +17 -0
- package/dist/types/gl/Light.d.ts +7 -0
- package/dist/types/gl/Lightmap.d.ts +9 -0
- package/dist/types/gl/Material.d.ts +90 -2
- package/dist/types/gl/Mesh.d.ts +18 -1
- package/dist/types/gl/Model.d.ts +34 -0
- package/dist/types/gl/Particles.d.ts +13 -0
- package/dist/types/gl/Scene.d.ts +23 -0
- package/dist/types/gl/Texture.d.ts +29 -1
- package/dist/types/gl/animation/AnimationClip.d.ts +25 -12
- package/dist/types/gl/animation/DynamicBone.d.ts +173 -0
- package/dist/types/gl/{IK.d.ts → animation/IK.d.ts} +4 -4
- package/dist/types/gl/{Locomotion.d.ts → animation/Locomotion.d.ts} +6 -6
- package/dist/types/gl/animation/core.d.ts +15 -10
- package/dist/types/gl/audio/AudioSource.d.ts +60 -0
- package/dist/types/gl/audio/AudioZone.d.ts +32 -0
- package/dist/types/gl/audio/SceneAudio.d.ts +11 -0
- package/dist/types/gl/{NavAgent.d.ts → nav/NavAgent.d.ts} +4 -4
- package/dist/types/gl/{NavMesh.d.ts → nav/NavMesh.d.ts} +4 -4
- package/dist/types/gl/{CharacterController.d.ts → physics/CharacterController.d.ts} +5 -5
- package/dist/types/gl/{Physics.d.ts → physics/Physics.d.ts} +6 -5
- package/dist/types/gl/{Ragdoll.d.ts → physics/Ragdoll.d.ts} +4 -4
- package/dist/types/gl/{Shape.d.ts → physics/Shape.d.ts} +3 -3
- package/dist/types/gl/{Trigger.d.ts → physics/Trigger.d.ts} +2 -2
- package/dist/types/gl/state.d.ts +0 -1
- package/dist/types/gl/{Terrain.d.ts → terrain/Terrain.d.ts} +6 -6
- package/dist/types/gl/{terrainMesh.d.ts → terrain/terrainMesh.d.ts} +1 -1
- package/dist/types/gl/vehicle/Vehicle.d.ts +300 -0
- package/dist/types/gl/vehicle/Wheel.d.ts +147 -0
- package/dist/types/inject.d.ts +33 -21
- package/dist/types/runtime/files.d.ts +24 -1
- package/dist/types/runtime/input.d.ts +11 -0
- package/dist/types/scene/defineScene.d.ts +10 -3
- package/dist/types/ui/UIImage.d.ts +15 -5
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/README.md +142 -142
- package/prompts/dist/2d-game.md +408 -197
- package/prompts/dist/3d-app.md +491 -166
- package/prompts/dist/ar-app.md +373 -163
- package/prompts/dist/design.md +83 -87
- package/prompts/dist/ui-app.md +325 -136
- package/src/audio/Bus.ts +102 -0
- package/src/audio/Sound.ts +96 -0
- package/src/audio/Voice.ts +102 -0
- package/src/audio/audio.ts +161 -0
- package/src/audio/support.ts +6 -0
- package/src/bridges.d.ts +279 -32
- package/src/canvas/Canvas.ts +21 -0
- package/src/compile/__tests__/compile.test.ts +11 -0
- package/src/compile/compileProject.ts +30 -15
- package/src/compile/header.ts +6 -3
- package/src/compile/index.ts +4 -0
- package/src/compile/sceneEditor.ts +42 -1
- package/src/core/Aspect.ts +33 -8
- package/src/g2/Scene2D.ts +7 -0
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/DecalSet.ts +360 -0
- package/src/gl/Geometry.ts +348 -279
- package/src/gl/Light.ts +16 -0
- package/src/gl/Lightmap.ts +35 -7
- package/src/gl/Material.ts +173 -4
- package/src/gl/Mesh.ts +120 -83
- package/src/gl/Model.ts +33 -1
- package/src/gl/Node.ts +1 -1
- package/src/gl/Particles.ts +21 -3
- package/src/gl/Scene.ts +41 -7
- package/src/gl/Texture.ts +43 -3
- package/src/gl/animation/AnimationClip.ts +43 -20
- package/src/gl/animation/Animator.ts +4 -3
- package/src/gl/animation/DynamicBone.ts +459 -0
- package/src/gl/{IK.ts → animation/IK.ts} +4 -4
- package/src/gl/{Locomotion.ts → animation/Locomotion.ts} +7 -7
- package/src/gl/animation/core.ts +20 -15
- package/src/gl/audio/AudioSource.ts +113 -0
- package/src/gl/audio/AudioZone.ts +75 -0
- package/src/gl/audio/SceneAudio.ts +26 -0
- package/src/gl/{NavAgent.ts → nav/NavAgent.ts} +5 -5
- package/src/gl/{NavMesh.ts → nav/NavMesh.ts} +8 -8
- package/src/gl/{CharacterController.ts → physics/CharacterController.ts} +5 -5
- package/src/gl/{Physics.ts → physics/Physics.ts} +12 -5
- package/src/gl/{Ragdoll.ts → physics/Ragdoll.ts} +272 -270
- package/src/gl/{Shape.ts → physics/Shape.ts} +3 -3
- package/src/gl/{Trigger.ts → physics/Trigger.ts} +2 -2
- package/src/gl/{physicsEvents.ts → physics/physicsEvents.ts} +1 -1
- package/src/gl/scenarios.ts +291 -291
- package/src/gl/state.ts +1 -1
- package/src/gl/{Terrain.ts → terrain/Terrain.ts} +10 -10
- package/src/gl/{terrainMesh.ts → terrain/terrainMesh.ts} +1 -1
- package/src/gl/vehicle/Vehicle.ts +666 -0
- package/src/gl/vehicle/Wheel.ts +290 -0
- package/src/inject.ts +226 -212
- package/src/runtime/files.ts +32 -2
- package/src/runtime/input.ts +6 -1
- package/src/scene/defineScene.ts +26 -10
- package/src/scene/gizmos.ts +148 -148
- package/src/scene/level.ts +2 -2
- package/src/ui/UIImage.ts +21 -7
- package/dist/types/gl/Gearbox.d.ts +0 -86
- package/dist/types/gl/Vehicle.d.ts +0 -191
- package/dist/types/gl/Wheel.d.ts +0 -95
- package/src/gl/Gearbox.ts +0 -212
- package/src/gl/Vehicle.ts +0 -473
- package/src/gl/Wheel.ts +0 -240
- /package/dist/types/gl/{physicsEvents.d.ts → physics/physicsEvents.d.ts} +0 -0
package/src/bridges.d.ts
CHANGED
|
@@ -106,6 +106,8 @@ declare global {
|
|
|
106
106
|
* flat skybox. */
|
|
107
107
|
setSkyboxTexture?(sceneId: number, fetchId: number): void
|
|
108
108
|
setSceneMultiSampleAntiAliasing(sceneId: number, enabled: boolean, scale: number): void
|
|
109
|
+
/** Filament View::setStencilBufferEnabled (Scene.stencil). Optional. */
|
|
110
|
+
setSceneStencil?(sceneId: number, enabled: boolean): void
|
|
109
111
|
/** Render resolution: `renderScale` (0.25–1) = fixed 3D-buffer scale vs the viewport (the UI is
|
|
110
112
|
* untouched; a host that owns the 3D texture resizes it, one rendering into the swapchain may
|
|
111
113
|
* ignore it), `dynamicResolution` = engine-adaptive scaling under that down to `minScale`.
|
|
@@ -118,6 +120,23 @@ declare global {
|
|
|
118
120
|
* anisotropy` does that automatically (a scene's env runs before its nodes build). Optional:
|
|
119
121
|
* hosts that predate it keep isotropic filtering and the SDK skips the call. */
|
|
120
122
|
setTextureAnisotropy?(level: number): void
|
|
123
|
+
/** Engine-wide cap on texture size (`Texture.maxSize` / `SceneOptions.maxTextureSize`): a
|
|
124
|
+
* KTX2 wider or taller than `size` loses its top mip levels on load, a glTF PNG/JPEG is
|
|
125
|
+
* downsampled; 0 = no cap. Reaches textures created AFTER the call — a loaded level keeps
|
|
126
|
+
* its textures, so a settings menu applies it on the next level load. `createTexture` flag
|
|
127
|
+
* 2 (FULL_SIZE) exempts one texture (lightmap pages). Optional: hosts that predate it load
|
|
128
|
+
* full-size textures and the SDK skips the call. */
|
|
129
|
+
setTextureMaxSize?(size: number): void
|
|
130
|
+
/** Depth-reading effects on / off (`scene.setDepthEffects`): soft particles (`depthFade`) and
|
|
131
|
+
* projected decals read the scene depth, which costs a half-res depth pre-pass of every opaque
|
|
132
|
+
* draw. Off = no pre-pass, hard-edged particles, decal sets draw nothing. Engine-wide, live.
|
|
133
|
+
* Optional: hosts that predate it keep the effects and the SDK skips the call. */
|
|
134
|
+
setDepthEffects?(enabled: boolean): void
|
|
135
|
+
/** LOD distance (`scene.setLodBias`): the LOD pass' screen-size thresholds × `bias` — 2 = every
|
|
136
|
+
* level switches at half the distance, 0.5 = full detail twice as far; 1 = the defaults
|
|
137
|
+
* (0.30 / 0.12 / 0.05 of the viewport height). Engine-wide, live, clamped 0.25..8. Optional:
|
|
138
|
+
* hosts without the LOD pass ignore it and the SDK skips the call. */
|
|
139
|
+
setLodBias?(bias: number): void
|
|
121
140
|
setMaterialGlobalParameter(sceneId: number, i: number, x: number, y: number, z: number, w: number): void
|
|
122
141
|
getCameraFov(sceneId: number, fovType: number): number
|
|
123
142
|
/** The scene camera's projection: VERTICAL fov in degrees + near/far clip distances (defaults
|
|
@@ -136,7 +155,9 @@ declare global {
|
|
|
136
155
|
|
|
137
156
|
/** `uv1` (optional, 2 floats per vertex) is the lightmap UV set (Geometry.uv1); absent → the
|
|
138
157
|
* host duplicates `uv` into UV1. Hosts that predate the argument ignore it. */
|
|
139
|
-
|
|
158
|
+
/** `colors` (Geometry.colors) is 4 bytes RGBA per vertex for a `requires: [color]` material;
|
|
159
|
+
* a host that predates it draws the mesh white. */
|
|
160
|
+
setMesh(entityId: number, materialId: number, vertices: Float32Array, normals: Float32Array, indices: Uint16Array, uv: Float32Array, meshType: number, uv1?: Float32Array, colors?: Uint8Array): void
|
|
140
161
|
// Terrain (gl/Terrain.ts ↔ creator-gl/src/terrain.cpp, docs/terrain-plan.md §1.4): a heightmap grid of
|
|
141
162
|
// sizeX × sizeZ samples `cellSize` apart (+X across columns, +Z across rows, height on +Y, sample 0 at
|
|
142
163
|
// the node origin), drawn as one renderable per `chunk`×`chunk` cells on internal children of the
|
|
@@ -157,10 +178,26 @@ declare global {
|
|
|
157
178
|
setInstancedMeshMaterial(entityId: number, materialId: number): void
|
|
158
179
|
setInstancedMeshShadows(entityId: number, cast: boolean, receive: boolean): void
|
|
159
180
|
setCulling(entityId: number, culling: boolean): void
|
|
181
|
+
/** Filament RenderableManager::setPriority — the coarse draw order, 0..7 (Mesh.renderPriority).
|
|
182
|
+
* Optional: an older host leaves everything at the default 4. */
|
|
183
|
+
setRenderPriority?(entityId: number, priority: number): void
|
|
184
|
+
/** Filament MaterialInstance::setDepthCulling / setDepthWrite — per-instance overrides of the
|
|
185
|
+
* depth state baked into the shader package (Material.depthTest / depthWrite). Optional. */
|
|
186
|
+
setMaterialDepthTest?(materialInstanceId: number, enable: boolean): void
|
|
187
|
+
setMaterialDepthWrite?(materialInstanceId: number, enable: boolean): void
|
|
188
|
+
/** Filament MaterialInstance::setCullingMode: 0 none (double-sided), 1 front, 2 back. Optional. */
|
|
189
|
+
setMaterialCulling?(materialInstanceId: number, mode: number): void
|
|
190
|
+
/** Filament MaterialInstance stencil state in one call (Material.stencil): `test` 0 always, 1 never,
|
|
191
|
+
* 2 less, 3 lessEqual, 4 greater, 5 greaterEqual, 6 equal, 7 notEqual; the ops 0 keep, 1 zero,
|
|
192
|
+
* 2 replace, 3 increment, 4 decrement, 5 invert. Optional. */
|
|
193
|
+
setMaterialStencil?(materialInstanceId: number, write: boolean, test: number, ref: number, onPass: number, onFail: number, onDepthFail: number, readMask: number, writeMask: number): void
|
|
160
194
|
setCastShadows(entityId: number, culling: boolean): void
|
|
161
195
|
setReceiveShadows(entityId: number, culling: boolean): void
|
|
162
196
|
|
|
163
|
-
|
|
197
|
+
/** Decode a fetched image into a texture. `flags` (optional, Texture.load): bit 1 = LINEAR data
|
|
198
|
+
* (a normal map — store RGBA8, not sRGB); unset / absent = colour, sRGB. A host that ignores
|
|
199
|
+
* it loads colour correctly and normal maps wrongly. KTX2 decides by its own header. */
|
|
200
|
+
createTexture(systemId: number, onComplete: (id: number, width: number, height: number) => void, onReject: () => void, flags?: number): void
|
|
164
201
|
// Texture from a baked _creatorCanvas surface (RGBA8, already rasterized — synchronous, no decode).
|
|
165
202
|
createTextureFromCanvas(surfaceId: number): number
|
|
166
203
|
updateTextureFromCanvas(texId: number, surfaceId: number): void
|
|
@@ -237,9 +274,11 @@ declare global {
|
|
|
237
274
|
// interp 0 linear/1 step/2 cubic, comps, keyCount, times…, values…)*]. Returns a one-clip set id (0 = bad).
|
|
238
275
|
createClipFromTracks(names: string, data: Float32Array): number
|
|
239
276
|
getClipSetInfo(clipSetId: number): { name: string, duration: number, trackCount: number }[]
|
|
240
|
-
// A
|
|
241
|
-
//
|
|
242
|
-
|
|
277
|
+
// A clip DERIVED from one of the set as a NEW single-clip set (index 0) — AnimationClip.from(clip, { mirror,
|
|
278
|
+
// from, to }): the mirror (left ↔ right on the set's own rig, the GLB's node tree: contacts swapped, heading
|
|
279
|
+
// negated), then the [start, end] window re-timed to 0 (start < 0 = whole, end < 0 = the clip's end; boundary
|
|
280
|
+
// values interpolated in, events re-timed). 0 = bad id / range, or a mirror asked of a set without a rig.
|
|
281
|
+
deriveClip(clipSetId: number, clip: number, mirror: boolean, start: number, end: number): number
|
|
243
282
|
// Clip events on the CLIP: normalized times (sorted ascending). Every slot bound to the clip, in every
|
|
244
283
|
// animator, fires slot event type 4 + i on crossing event i. Empty = clear.
|
|
245
284
|
setClipEvents(clipSetId: number, clip: number, times: Float32Array): void
|
|
@@ -304,7 +343,7 @@ declare global {
|
|
|
304
343
|
// One curve of a slot's clip at `time` seconds (< 0 = the slot's clock now): which 0 φ (-1 = no gait)
|
|
305
344
|
// / 1 travel (m) / 2 yaw (rad, + = left) / 3 speed (m/s) / 4-5 unit travel direction x / z
|
|
306
345
|
// (model space, held through stills — integrate dir × d(travel) for the root's 2D path).
|
|
307
|
-
animatorSlotCurveAt(animatorId: number, slot: number, which: 0 | 1 | 2 | 3 | 4 | 5, time: number): number
|
|
346
|
+
animatorSlotCurveAt(animatorId: number, slot: number, which: 0 | 1 | 2 | 3 | 4 | 5 | 6, time: number): number
|
|
308
347
|
// out ← [curve sample dt, total travel (m), mean speed (m/s), in-place flag]. False = unbound slot.
|
|
309
348
|
animatorSlotCurveInfo(animatorId: number, slot: number, out: Float32Array): boolean
|
|
310
349
|
animatorSlotTurn(animatorId: number, slot: number): number // the clip's total root yaw, rad
|
|
@@ -445,6 +484,12 @@ declare global {
|
|
|
445
484
|
* [minX,minY,minZ, maxX,maxY,maxZ] — zeros for an empty / not-yet-loaded subtree. What
|
|
446
485
|
* `Shape.fit()` measures. Optional: absent on hosts predating the binding. */
|
|
447
486
|
computeBoundingBox?(entityId: number): Float32Array
|
|
487
|
+
/** DEBUG pick: the closest TRIANGLE under a screen point (logical px) among the loaded GLB instances —
|
|
488
|
+
* entityId = one Model's root, 0 = every instance — CPU-skinned with the joints' CURRENT pose, both
|
|
489
|
+
* faces. JSON string (`Model.pickTriangle` parses it: node, mesh, primitive, triangle, hit point,
|
|
490
|
+
* the three vertices with their raw JOINTS_0/WEIGHTS_0 pairs), "" on a miss. One full CPU skin per
|
|
491
|
+
* call — click-rate only. Optional: a debug tool, hosts may lack it. */
|
|
492
|
+
pickTriangle?(entityId: number, screenX: number, screenY: number): string
|
|
448
493
|
setColliderFromMesh(entityId: number, meshEntityId: number, form: number): void
|
|
449
494
|
setColliderBox(entityId: number, centerX: number, centerY: number, centerZ: number, sizeX: number, sizeY: number, sizeZ: number): void
|
|
450
495
|
setColliderSphere(entityId: number, centerX: number, centerY: number, centerZ: number, radius: number): void
|
|
@@ -462,7 +507,9 @@ declare global {
|
|
|
462
507
|
lightmapHasSupport?(): boolean
|
|
463
508
|
/** Lightmap consumption (docs/lightmap-plan.md §3): the next createGlb takes lightmap.filamat (the material
|
|
464
509
|
* behind that instance id) instead of the ubershader and keeps TEXCOORD_1; UINT32_MAX clears. */
|
|
465
|
-
|
|
510
|
+
/** `maskedMaterialInstanceId` (2026-09-11): the tier's masked twin for the asset's alpha-MASK materials
|
|
511
|
+
* (foliage cards); a host without it ignores the argument and keeps those on the ubershader. */
|
|
512
|
+
setNextGlbLightmapped?(materialInstanceId: number, maskedMaterialInstanceId?: number): void
|
|
466
513
|
/** The level-wide knobs every lightmap-material instance shares (applied to the ones that exist and to every one
|
|
467
514
|
* created afterwards): the sun-mask shadow math (ambientScale, sunStrength), how much baked AO applies (aoStrength,
|
|
468
515
|
* 1 = all) and the light atlas' / volume's physical irradiance per encoded 1.0 (lightScale; 0 = no baked lights).
|
|
@@ -567,43 +614,86 @@ declare global {
|
|
|
567
614
|
characterSetPosition(charId: number, x: number, y: number, z: number): void
|
|
568
615
|
// Vehicle (Jolt VehicleConstraint + WheeledVehicleController). One settings BLOB, so tuning knobs
|
|
569
616
|
// never grow this ABI — layout (floats):
|
|
570
|
-
// header[
|
|
571
|
-
// engTorque, engMaxRpm, engIdleRpm
|
|
617
|
+
// header[34]: version(15), mass, comAuto, comY,
|
|
618
|
+
// engTorque, engMaxRpm, engIdleRpm (0 = no floor: the engine can stall, the game
|
|
619
|
+
// holds idle and cranks it), engInertia, engBraking, clutchStrength,
|
|
572
620
|
// diffRatio (<= 0 = a fully OPEN differential),
|
|
573
621
|
// antiRoll (the bar's stiffness as a FRACTION of the wheel spring; 0 = no bars),
|
|
574
622
|
// maxTiltDeg,
|
|
575
|
-
// steerLockDeg, steerAtSpeedDeg, steerSpeedMs, steerRateDeg (speed-sensitive
|
|
576
|
-
// steering: the lock falls to steerAtSpeedDeg by steerSpeedMs and the wheels turn
|
|
577
|
-
// no faster than steerRateDeg per second; 0/0/0 = the raw lock, instantly),
|
|
578
623
|
// aeroDownforce, aeroDrag (each a fraction of the car's own WEIGHT at 30 m/s, scaled
|
|
579
624
|
// by v² from there; 0/0 = no aero, Jolt's own behaviour),
|
|
625
|
+
// steerMode (0 = `steer` is the wheel angle as a fraction of the lock; 1 = `steer`
|
|
626
|
+
// is where the driver's HANDS aim a steering column the engine integrates every
|
|
627
|
+
// sub-step: I·θ̈ = T_hand + T_align·(1 − assist) + T_stop − damping·θ̇ − friction,
|
|
628
|
+
// T_hand = clamp(handStiffness·(steer·lock − θ), ±handTorque·steerForce),
|
|
629
|
+
// steerForce = the input's per-frame hold — the game's policy on when the hands let go,
|
|
630
|
+
// T_align = the steered tires' lateral force × (pneumatic trail collapsing to
|
|
631
|
+
// trailFloor × trail at the curve's peak + caster) — heavy at speed, light past
|
|
632
|
+
// the peak, self-centring, counter-steering in a slide),
|
|
633
|
+
// colInertia, colDamping, colFriction (the patch's dry friction, fades out by 1.5 m/s),
|
|
634
|
+
// colCaster, colTrail, colTrailFloor (the aligning torque's arms — read in BOTH
|
|
635
|
+
// modes; steerTorque is reported either way, the force-feedback signal),
|
|
636
|
+
// colHandTorque, colHandStiffness, colAssist (power steering),
|
|
637
|
+
// colRateDeg (the HANDS' top turning speed, deg/s — they cannot push a wheel that
|
|
638
|
+
// outruns them, they can still hold it; the free column is uncapped; 0 = none),
|
|
639
|
+
// colStopDeg, colStopTorque, colStopDamping (the end stop: over the last colStopDeg
|
|
640
|
+
// before the lock the rack pushes back colStopTorque × depth² N·m and damps by
|
|
641
|
+
// colStopDamping × depth N·m·s/rad — progressive, viscous rubber; 0 band = the
|
|
642
|
+
// hard clamp only),
|
|
643
|
+
// colBearing (the column's own dry friction, N·m, at any speed — rack and bearings),
|
|
644
|
+
// colHandRamp (seconds the hands' torque builds to colHandTorque over; 0 = instant),
|
|
645
|
+
// clutchCapacity (N·m the clutch passes before it slips, × the clutch scalar; 0 = Jolt's
|
|
646
|
+
// viscous clutch alone),
|
|
580
647
|
// wheelCount, curveCount
|
|
581
648
|
// + curveCount * 2: the engine's normalized torque curve (x = rpm/maxRpm, y = torque/maxTorque);
|
|
582
649
|
// 0 points keeps Jolt's default
|
|
583
|
-
// +
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
//
|
|
650
|
+
// + per wheel, VARIABLE length: 15 fixed floats — px, py, pz, radius, width, maxSteerDeg,
|
|
651
|
+
// driven (the wheel's SHARE of the engine's torque: an axle's share is the sum
|
|
652
|
+
// of its two, the left/right split their ratio; every wheel 0 = no drive at
|
|
653
|
+
// all), axle, travel, stiffness, damping,
|
|
654
|
+
// traction (the longitudinal impulse clamp as a multiple of friction × load;
|
|
655
|
+
// 1 = the physical tire, Jolt's sample runs 10), circle (friction circle 0..1:
|
|
656
|
+
// the share of lateral capacity the longitudinal impulse in use takes away;
|
|
657
|
+
// 0 = the two axes independent), sideCount, forwardCount — then
|
|
658
|
+
// sideCount × (slip angle °, friction) and forwardCount × (slip ratio,
|
|
659
|
+
// friction). Tire curves are POINTS the SDK sends (its presets are SDK-side);
|
|
660
|
+
// 0 points keeps Jolt's own curve.
|
|
661
|
+
// In steerMode 0 the steering input is RAW: `steer` × each wheel's maxSteerDeg, per fixed step —
|
|
662
|
+
// any taper / rate limit is the game's. In mode 1 `steer` is where the hands aim the wheel.
|
|
663
|
+
// NO gear list, no pedals and no wheel node ids: the gearbox is the game's (a ratio + clutch in
|
|
664
|
+
// the input vector), the brakes are a torque per wheel in the same vector, and the game poses its
|
|
665
|
+
// wheel models itself from the state. Chassis space is forward -Z / up +Y (matching node.forward);
|
|
587
666
|
// wheel positions are suspension attachment points in unscaled chassis space; `axle` pairs wheels
|
|
588
667
|
// for the differentials + anti-roll bars.
|
|
589
668
|
vehicleCreate(entityId: number, shapeId: number, settings: Float32Array): number
|
|
590
669
|
vehicleDestroy(vehicleId: number): void
|
|
591
|
-
/**
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
*
|
|
599
|
-
|
|
600
|
-
|
|
670
|
+
/** ONE input vector, latched and applied once per fixed step: [throttle 0..1, steer −1..1 (a
|
|
671
|
+
* fraction of the wheels' maxSteerDeg, applied as is; with a column, where the hands aim),
|
|
672
|
+
* steerForce 0..1 (with a column: how firmly the hands hold the wheel — the game's per-frame
|
|
673
|
+
* policy on letting go; without one, ignored), ratio (the ONE gear ratio the car runs: 0 =
|
|
674
|
+
* neutral, negative = reverse — the engine never sees a gear list), clutch 0..1 (scales the clutch
|
|
675
|
+
* in the coupled engine/wheel solve), brake_0 … brake_n (N·m of brake torque per wheel, this
|
|
676
|
+
* step)]. A shorter vector leaves the rest as it was. Sticky; a non-zero throttle, steer or brake
|
|
677
|
+
* wakes a sleeping car. */
|
|
678
|
+
vehicleSetInput(vehicleId: number, input: Float32Array): void
|
|
679
|
+
/** Re-apply the TUNABLE half of the blob (same layout) to a live car — differential, per-wheel tire
|
|
680
|
+
* curves / traction / circle, engine torque/RPM/curve, clutch strength + capacity, steer lock, the
|
|
681
|
+
* column, anti-roll stiffness, tilt limit. Structural values (mass, centre of mass, wheel geometry,
|
|
682
|
+
* the driven shares, suspension, whether an axle has a bar) are ignored: those need a re-create. */
|
|
601
683
|
vehicleSetTuning?(vehicleId: number, settings: Float32Array): void
|
|
602
|
-
/** out = [speed, rpm, wheelsInContact, vx, vy, vz]
|
|
603
|
-
*
|
|
604
|
-
*
|
|
684
|
+
/** out = [speed, rpm, wheelsInContact, vx, vy, vz, wx, wy, wz, steerDeg, steerTorque] (w = chassis
|
|
685
|
+
* angular velocity, rad/s; steerDeg = where the road wheels are, right-positive; steerTorque = the
|
|
686
|
+
* tires' self-aligning torque on the steering, N·m, + = pulls right — the force-feedback signal,
|
|
687
|
+
* reported in both steering modes)
|
|
688
|
+
* + per wheel [contact, slipLong, slipAngleDeg, suspensionLength, steerDeg, spin, fLat, fLong, vLat, vSlip, load]
|
|
689
|
+
* (fLat/fLong = the tire's forces, N, + = to its right / pushing the car forward; vLat/vSlip = the
|
|
690
|
+
* patch's sliding speeds, m/s, + = to the right / tread spinning up — force × speed is heat; load =
|
|
691
|
+
* the suspension's force on the wheel, N). slipLong is SIGNED (+ spinning up, − locking) and so is
|
|
692
|
+
* slipAngleDeg (+ the patch sliding to the tire's right). suspensionLength, steerDeg and spin are
|
|
693
|
+
* what a wheel model's pose is built from — by the game, the SDK only exposes them. */
|
|
605
694
|
vehicleGetState(vehicleId: number, out: Float32Array): void
|
|
606
|
-
/** Teleport upright and clear all motion (velocities, engine RPM,
|
|
695
|
+
/** Teleport upright and clear all motion (velocities, engine RPM, wheel spin); the input vector is
|
|
696
|
+
* zeroed — the SDK re-sends its own on the next early pass. */
|
|
607
697
|
vehicleReset(vehicleId: number, x: number, y: number, z: number, qx: number, qy: number, qz: number, qw: number): void
|
|
608
698
|
/** The chassis rigid body, for the plain body calls (physicsApplyImpulse, …). 0 if unknown. */
|
|
609
699
|
vehicleBodyId(vehicleId: number): number
|
|
@@ -644,6 +734,61 @@ declare global {
|
|
|
644
734
|
ragdollActive?(ragdollId: number): boolean
|
|
645
735
|
/** The rigid body of part `index` (for physicsApplyImpulseAt / velocities). 0 if unknown. */
|
|
646
736
|
ragdollBodyId?(ragdollId: number, index: number): number
|
|
737
|
+
// Dynamic bones (the SDK's DynamicBone; creator-anim canimDyn* behind creator-gl): secondary motion
|
|
738
|
+
// for tails, ears, hair and cloaks — a bone tree under one root simulated as Verlet particle chains
|
|
739
|
+
// (gravity, wind, drag, stiffness toward the animated shape, an angle cone, capsule / sphere
|
|
740
|
+
// colliders, a floor plane, bone length, neighbour links) in an engine stage AFTER the late phase
|
|
741
|
+
// and BEFORE the skin flush: the animator, an active ragdoll and late-phase JS bone writes are the
|
|
742
|
+
// input, the simulated local rotations the output. Bones the animator does not drive keep their
|
|
743
|
+
// bind pose as the target; driven ones follow their clip. Optional: a host without it shows the
|
|
744
|
+
// animation alone.
|
|
745
|
+
// bones[boneCount]: entity ids — the root first, every other bone a child of an earlier one.
|
|
746
|
+
// settings — header[28]: version(1), boneCount, boneStride(7), then the chain params: weight
|
|
747
|
+
// (0..1 animation → simulation; 0 = off, re-arms on the animated pose), follow (0..1 of
|
|
748
|
+
// the root's travel carried onto the particles; 0 = full whip), wind xyz (m/s², world),
|
|
749
|
+
// link (0..1 neighbour-link strength), rate (substep Hz, ≤ 4 substeps per frame),
|
|
750
|
+
// teleport (a root jump past this many metres resets the chain), floor (0/1), floor
|
|
751
|
+
// point xyz, floor normal xyz (the floor slots are overridden by dynamicBoneSetFloor),
|
|
752
|
+
// floorFriction (a Coulomb coefficient: a resting particle's slide loses up to friction · g · dt of
|
|
753
|
+
// speed per substep), iterations
|
|
754
|
+
// (constraint passes per substep, 1..8, default 4; a long rope may want 8), side (the
|
|
755
|
+
// cloth's outside, one-sided colliders: 0 none, 1 away from the root bone's axis, 2 the
|
|
756
|
+
// next xyz in the root bone's frame), side xyz, guideHold (0..1: guides re-applied after
|
|
757
|
+
// every constraint pass with this fraction of their weight; 0 = once before the passes),
|
|
758
|
+
// edges (1 = the bone segments and links collide with the capsules too, not only the particles),
|
|
759
|
+
// spin (> 0: the chain in the parent bone's rotating frame + its centrifugal / Euler forces × spin;
|
|
760
|
+
// 0 = the translational frame, no turn forces), spinInertia (s: the cloth's own rotation follows the body's with this time constant — behind on a start, past the back on a stop; 0 = glued)
|
|
761
|
+
// + boneCount × 7: radius (m), stiffness (0..1 per 1/60 s toward the animated shape), damping
|
|
762
|
+
// (0..1 velocity lost per 1/60 s), gravity (m/s² along world −Y), angleLimit (degrees off
|
|
763
|
+
// the animated direction, 0 = none), mass (relative, 0 = 1: a bone-length constraint
|
|
764
|
+
// moves its two ends in inverse proportion; the root is kinematic; < 0 = PINNED, the bone
|
|
765
|
+
// rides the animation), give (m, pinned bones: a soft pin — a collider may push the bone
|
|
766
|
+
// this far off its animated place, its local translation is written back too; 0 = hard)
|
|
767
|
+
// links: (a, b) bone index pairs held at their rest distance (a cloak's columns; two linked leaves
|
|
768
|
+
// link their virtual tips too, so a hem stays a hem); optional.
|
|
769
|
+
dynamicBoneCreate?(modelRootId: number, bones: Uint32Array, settings: Float32Array, links?: Uint16Array): number
|
|
770
|
+
dynamicBoneDestroy?(id: number): void
|
|
771
|
+
/** Retune a live chain: the same blob as create (the bone rows too when the count matches). */
|
|
772
|
+
dynamicBoneSet?(id: number, settings: Float32Array): void
|
|
773
|
+
/** The floor the particles stay above: mode 0 none / 1 the plane (point, normal) / 2 probe — the
|
|
774
|
+
* engine casts a ray from the chain root down every frame (the ground probe, the model root
|
|
775
|
+
* excluded) and uses the hit; no hit / no physics world = no floor that frame. */
|
|
776
|
+
dynamicBoneSetFloor?(id: number, mode: 0 | 1 | 2, x: number, y: number, z: number, nx: number, ny: number, nz: number): void
|
|
777
|
+
/** The next frame snaps the chain onto the animated pose (a teleport, a cut). */
|
|
778
|
+
dynamicBoneReset?(id: number): void
|
|
779
|
+
/** The colliders this chain collides with (dynamicBoneColliderCreate ids); replaces the list. */
|
|
780
|
+
dynamicBoneSetColliders?(id: number, colliderIds: Uint32Array): void
|
|
781
|
+
/** The particles' world positions (bones first, then the leaves' virtual tips), 3 floats each into
|
|
782
|
+
* `out`; returns the count written — a debug overlay. */
|
|
783
|
+
dynamicBoneParticles?(id: number, out: Float32Array): number
|
|
784
|
+
// guides: rows × 5 — bone index (in the chain's bone order), world x y z, weight — world points the
|
|
785
|
+
// bones' particles are drawn to before the constraints; null / empty clears; refreshed every frame
|
|
786
|
+
dynamicBoneTargets?(id: number, rows: Float32Array | null): void
|
|
787
|
+
/** A capsule a → b in the entity's local space (a == b = a sphere) that rides the entity — a bone
|
|
788
|
+
* of the body the chains must not pass through. Returns a collider id (0 on failure). */
|
|
789
|
+
dynamicBoneColliderCreate?(entityId: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, radius: number): number
|
|
790
|
+
dynamicBoneColliderSet?(colliderId: number, ax: number, ay: number, az: number, bx: number, by: number, bz: number, radius: number): void
|
|
791
|
+
dynamicBoneColliderDestroy?(colliderId: number): void
|
|
647
792
|
// Legacy coupled shape+body (still used by the worker RigidBody).
|
|
648
793
|
physicsCreateBox(entityId: number, hx: number, hy: number, hz: number, motionType: number, mass: number): number
|
|
649
794
|
physicsCreateSphere(entityId: number, radius: number, motionType: number, mass: number): number
|
|
@@ -681,6 +826,23 @@ declare global {
|
|
|
681
826
|
spawnParticles(entityId: number, count: number): void
|
|
682
827
|
setParticleSystemConfig(entityId: number, data: Float32Array): void
|
|
683
828
|
|
|
829
|
+
// Projected decals (creator-gl src/decals.h; SDK gl/DecalSet.ts). A set on an entity = one
|
|
830
|
+
// renderable of `capacity` (0 = 256) unit boxes drawn with the material instance (decal.filamat)
|
|
831
|
+
// — each box projects its atlas cell onto the opaque scene behind it through the scene depth
|
|
832
|
+
// buffer. A record is 27 floats in the SET entity's space: X Y Z axes scaled by the box's
|
|
833
|
+
// width / height / depth (Z = out of the surface), centre, atlas rect u0 v0 u1 v1, tint rgba,
|
|
834
|
+
// life (s, 0 = forever), fadeIn (s), fadeOut (s), capStart, capEnd (tilt of the image's bottom /
|
|
835
|
+
// top edge in unit space — mitred trail joints, 0 = square), alphaStart, alphaEnd (opacity
|
|
836
|
+
// multipliers at the bottom / top edge — a gradient along the image). addDecal returns the slot (0xFFFFFFFF = no
|
|
837
|
+
// set; a full set recycles its oldest); updateDecal keeps the slot's birth time. Optional:
|
|
838
|
+
// a host without them draws no decals (the SDK warns once).
|
|
839
|
+
createDecalSet?(entityId: number, materialInstanceId: number, capacity: number): void
|
|
840
|
+
addDecal?(entityId: number, record: Float32Array): number
|
|
841
|
+
updateDecal?(entityId: number, slot: number, record: Float32Array): void
|
|
842
|
+
removeDecal?(entityId: number, slot: number): void
|
|
843
|
+
clearDecals?(entityId: number): void
|
|
844
|
+
decalCount?(entityId: number): number
|
|
845
|
+
|
|
684
846
|
createNoise(): number
|
|
685
847
|
setNoiseFrequency(noiseId: number, frequency: number): void
|
|
686
848
|
setNoiseOctaves(noiseId: number, octaves: number): void
|
|
@@ -960,6 +1122,72 @@ declare global {
|
|
|
960
1122
|
crowdRead(crowdId: number, out: Float32Array): number
|
|
961
1123
|
}
|
|
962
1124
|
|
|
1125
|
+
// ---- Game audio (creator-audio) ----------------------------------------------------------------
|
|
1126
|
+
// docs/audio-plan.md. Whole clips decoded up front, a fixed voice pool (play allocates nothing),
|
|
1127
|
+
// 3D sources the engine follows per frame, mixer buses with insert effects, reverb zones,
|
|
1128
|
+
// occlusion. OPTIONAL as a block: the SDK gates on `typeof _creatorAudio !== 'undefined' &&
|
|
1129
|
+
// hasSupport()` and stays inert (silent Sound, inert Voice) without it. Present on hosts built with
|
|
1130
|
+
// CREATOR_PKG_AUDIO (desktop first; Android / Apple pending — parity `audio-game`). The headless
|
|
1131
|
+
// renderer provides a RECORDER (plays are logged, nothing is mixed). Every parameter block is a
|
|
1132
|
+
// fixed-order Float32Array (creator-audio.h CAUD_SRC_* / the effect param orders).
|
|
1133
|
+
var _creatorAudio: {
|
|
1134
|
+
hasSupport(): boolean
|
|
1135
|
+
/** Decode the bytes behind a fetch system id (WAV / MP3 / FLAC / OGG Vorbis) on the engine's
|
|
1136
|
+
* loader thread; mono unless `stereo`. onDone(clipId, durationSeconds, channels). */
|
|
1137
|
+
loadClip(systemId: number, stereo: boolean, onDone: (clipId: number, duration: number, channels: number) => void, onReject: (message: string) => void): void
|
|
1138
|
+
releaseClip(clipId: number): void
|
|
1139
|
+
/** Bus id by name (-1 unknown); 0 master, 1 sfx, 2 music, 3 ui, 4 voice. */
|
|
1140
|
+
busId(name: string): number
|
|
1141
|
+
/** Creates (or finds) an app-defined bus under master. -1 when the 16 slots are full. */
|
|
1142
|
+
createBus(name: string): number
|
|
1143
|
+
setBusVolume(bus: number, volume: number): void
|
|
1144
|
+
setBusMuted(bus: number, muted: boolean): void
|
|
1145
|
+
/** kind 1 reverb [roomSize, damping, width, mix, preDelay] · 2 echo [delay, decay, mix] ·
|
|
1146
|
+
* 3 lowpass [cutoffHz]; an empty array turns the effect off. Changes are smoothed. */
|
|
1147
|
+
setBusEffect(bus: number, kind: number, params: Float32Array): void
|
|
1148
|
+
stopBus(bus: number, fade: number): void
|
|
1149
|
+
/** A 3D emitter. `attachSource` binds it to an entity the engine follows every frame;
|
|
1150
|
+
* `setSourcePosition` places an unbound one (playAt). */
|
|
1151
|
+
createSource(): number
|
|
1152
|
+
attachSource(sourceId: number, entityId: number): void
|
|
1153
|
+
/** [minDistance, maxDistance, rolloff (0 none 1 inverse 2 linear 3 exp), coneInnerDeg,
|
|
1154
|
+
* coneOuterDeg, coneOuterGain, doppler, spread, occlusion (0/1), bus]. */
|
|
1155
|
+
setSourceParams(sourceId: number, params: Float32Array): void
|
|
1156
|
+
setSourcePosition(sourceId: number, x: number, y: number, z: number): void
|
|
1157
|
+
stopSource(sourceId: number, fade: number): void
|
|
1158
|
+
sourceVoices(sourceId: number): number
|
|
1159
|
+
destroySource(sourceId: number): void
|
|
1160
|
+
/** The listener entity; 0 = the active scene camera (the default). */
|
|
1161
|
+
setListener(entityId: number): void
|
|
1162
|
+
setListenerOptions(dopplerFactor: number): void
|
|
1163
|
+
/** HRTF binaural rendering for the `maxVoices` nearest spatial voices (headphones); the rest keep panning. */
|
|
1164
|
+
setHrtf(enabled: boolean, maxVoices: number): void
|
|
1165
|
+
/** Whether the engine's time scale (Time.scale) also pitches the sfx bus. Default false (pause = mute only). */
|
|
1166
|
+
setTimeScalePitch(enabled: boolean): void
|
|
1167
|
+
/** → a voice token (0 = nothing played: the pool refused, the clip is not ready). bus -1 = the
|
|
1168
|
+
* source's bus (sfx for 2D). pan is 2D only. */
|
|
1169
|
+
play(clipId: number, sourceId: number, bus: number, volume: number, pitch: number, loop: boolean, priority: number, fadeIn: number, startAt: number, pan: number): number
|
|
1170
|
+
setVoiceVolume(token: number, volume: number): void
|
|
1171
|
+
setVoicePitch(token: number, pitch: number): void
|
|
1172
|
+
setVoicePan(token: number, pan: number): void
|
|
1173
|
+
stopVoice(token: number, fade: number): void
|
|
1174
|
+
voicePlaying(token: number): boolean
|
|
1175
|
+
voiceTime(token: number): number
|
|
1176
|
+
/** THE ended listener (single slot per JS world): called once per tick with the tokens of the
|
|
1177
|
+
* voices that ended since the previous tick — natural end, stop, steal. */
|
|
1178
|
+
setOnEnded(callback: ((tokens: Float32Array) => void) | null): void
|
|
1179
|
+
stopAll(fade: number): void
|
|
1180
|
+
/** A reverb volume: shape 0 box (dims = full size) / 1 sphere (dims[0] = diameter), in the
|
|
1181
|
+
* entity's local units; `blend` metres of crossfade inside the border; reverb params as for
|
|
1182
|
+
* setBusEffect kind 1. The listener inside blends the zone's reverb onto `bus`. */
|
|
1183
|
+
createZone(): number
|
|
1184
|
+
attachZone(zoneId: number, entityId: number): void
|
|
1185
|
+
setZone(zoneId: number, shape: number, dims: Float32Array, blend: number, bus: number, reverb: Float32Array): void
|
|
1186
|
+
destroyZone(zoneId: number): void
|
|
1187
|
+
/** [voicesPlaying, voicesMono, voicesStereo, stolen, clips, clipBytes, peak, sampleRate, listenerZone, zoneBlend, hrtfVoices]. */
|
|
1188
|
+
stats(): Float32Array
|
|
1189
|
+
}
|
|
1190
|
+
|
|
963
1191
|
// ---- UI engine (creator-ui) ------------------------------------------------------------------
|
|
964
1192
|
var _creatorUI: {
|
|
965
1193
|
// Global back-press fallback (app.onBackPressed): a PROPERTY the SDK writes, not a call —
|
|
@@ -989,6 +1217,11 @@ declare global {
|
|
|
989
1217
|
// Content-property push. Props: "value" (input/textarea text), "focus" (boolean — focus/blur
|
|
990
1218
|
// the input, opening/dismissing the keyboard; rides this channel so programmatic focus needs
|
|
991
1219
|
// no new ABI method), plus element-specific ones ("src", "text", …).
|
|
1220
|
+
// An image "src" (here and at creation) is a url string, { _id } (a host buffer), { svg },
|
|
1221
|
+
// { canvasSurface } (a baked Canvas), or { scene2d: sceneId } — a LIVE 2D scene the host draws
|
|
1222
|
+
// into the node's laid-out box every frame with the scene's own camera (native: c2dDrawSceneGL
|
|
1223
|
+
// into a per-node framebuffer; no intrinsic size, the scene needs no openScene). The engine's
|
|
1224
|
+
// simulation must still step each frame for it (native: c2dTick when nothing is presented).
|
|
992
1225
|
updateNode(nodeId: number, prop: string, value: any): void
|
|
993
1226
|
setSourceRect(nodeId: number, x: number, y: number, w: number, h: number): void
|
|
994
1227
|
|
|
@@ -1100,7 +1333,7 @@ declare global {
|
|
|
1100
1333
|
disposeFetch(systemId: number): void
|
|
1101
1334
|
// ---- Local filesystem (the SDK `files` global) ----------------------------------------------
|
|
1102
1335
|
// The read/write/delete half of `fetchLocal`, for hosts where the app OWNS a filesystem
|
|
1103
|
-
// (desktop). All
|
|
1336
|
+
// (desktop). All seven are OPTIONAL and stand or fall together: a sandboxed host (web, mobile)
|
|
1104
1337
|
// implements none of them and `files.supported` is false there.
|
|
1105
1338
|
//
|
|
1106
1339
|
// ASYNC, like `fetch`: file IO must not block the frame, so each takes an onComplete/onReject
|
|
@@ -1130,6 +1363,16 @@ declare global {
|
|
|
1130
1363
|
deleteLocal?(path: string, recursive: boolean, onComplete: () => void, onReject: (err: Error) => void): void
|
|
1131
1364
|
/** Create a directory and any missing parents. An existing directory is success. */
|
|
1132
1365
|
mkdirLocal?(path: string, onComplete: () => void, onReject: (err: Error) => void): void
|
|
1366
|
+
/** List a directory (added 2026-09-11, same optional set). Settles with an array of entries —
|
|
1367
|
+
* `name` = basename, `path` = relative to the listed directory with `/` separators (equals
|
|
1368
|
+
* `name` unless `recursive`), `kind`, `size` in bytes (0 for a directory), `modified` in ms
|
|
1369
|
+
* since the epoch — SORTED by `path`, so two runs and two hosts agree. Symlinks are followed
|
|
1370
|
+
* for `kind`; anything that is neither file nor directory is skipped. A MISSING directory is a
|
|
1371
|
+
* null result (like the readers); a path that is a file, or an unreadable directory, rejects. */
|
|
1372
|
+
listLocal?(path: string, recursive: boolean, onComplete: (entries: { name: string; path: string; kind: "file" | "dir"; size: number; modified: number }[] | null) => void, onReject: (err: Error) => void): void
|
|
1373
|
+
/** One entry's metadata (`name`, `kind`, `size`, `modified` — see `listLocal`) for a file or a
|
|
1374
|
+
* directory, or null when there is nothing at `path`. */
|
|
1375
|
+
statLocal?(path: string, onComplete: (entry: { name: string; kind: "file" | "dir"; size: number; modified: number } | null) => void, onReject: (err: Error) => void): void
|
|
1133
1376
|
openFilePicker(onComplete: (res: any) => void, onReject: () => void, multiple: boolean, accept?: string): void
|
|
1134
1377
|
// Present the OS media share sheet for a host buffer (`systemId` — a File/response id), with an
|
|
1135
1378
|
// optional caption. iOS: the share sheet also offers "Save to Files"/Photos. Web: saves (downloads)
|
|
@@ -1158,7 +1401,11 @@ declare global {
|
|
|
1158
1401
|
/** Continuous input poll by channel id (SDK `InputChannel`): 0 MouseX, 1 MouseY (logical px,
|
|
1159
1402
|
* last known cursor position inside the viewport), 2 MouseDX, 3 MouseDY (motion accumulated
|
|
1160
1403
|
* during the PREVIOUS frame — unaccelerated/raw where the OS offers it, counts while locked),
|
|
1161
|
-
* 4 WheelX, 5 WheelY (wheel notches, previous frame), 6 PointerLocked (0/1), 7 GamepadCount
|
|
1404
|
+
* 4 WheelX, 5 WheelY (wheel notches, previous frame), 6 PointerLocked (0/1), 7 GamepadCount,
|
|
1405
|
+
* 8 PointerOnUI (0/1: the primary pointer went DOWN on something the UI claimed — an
|
|
1406
|
+
* interactive node, a scrollable, an editable, a modal backdrop — and is still held; stays 1
|
|
1407
|
+
* wherever the cursor goes until the release, so a drag that started on a HUD control never
|
|
1408
|
+
* becomes camera look; always 0 while pointer-locked and for touch-only hosts without a UI claim);
|
|
1162
1409
|
* gamepad p at 16 + 16·p: +0 Connected, +1 LeftX, +2 LeftY, +3 RightX, +4 RightY (−1..1,
|
|
1163
1410
|
* +Y = down like the web), +5 LeftTrigger, +6 RightTrigger (0..1). Unknown channel → 0.
|
|
1164
1411
|
* "Previous frame" = the host's own frame boundary (the event pump / rAF / fixed step), so
|
package/src/canvas/Canvas.ts
CHANGED
|
@@ -35,6 +35,11 @@ const OP = {
|
|
|
35
35
|
DRAW_IMAGE: 30,
|
|
36
36
|
} as const
|
|
37
37
|
|
|
38
|
+
// Above this many buffered values, a buffer that is STILL growing between bakes is a leak, not a
|
|
39
|
+
// drawing (a dense static picture is a few thousand; a 15 Hz graph without reset() passes this in
|
|
40
|
+
// seconds and doubles its frame cost every minute).
|
|
41
|
+
const CMD_GROWTH_WARN = 20000
|
|
42
|
+
|
|
38
43
|
const ALIGN = { left: 0, center: 1, right: 2, start: 3, end: 4 } as const
|
|
39
44
|
const BASELINE = { alphabetic: 0, top: 1, middle: 2, bottom: 3, hanging: 4, ideographic: 5 } as const
|
|
40
45
|
const JOIN = { miter: 0, round: 1, bevel: 2 } as const
|
|
@@ -76,6 +81,11 @@ export class Canvas {
|
|
|
76
81
|
// also refreshes the on-screen image node(s) via _creatorUI — the UI consumer the 2D/3D texture
|
|
77
82
|
// hooks below don't cover. Stays a bare flag so a non-UI canvas never references the UI bridge.
|
|
78
83
|
private _uiBound = false
|
|
84
|
+
// Growth watch: the command buffer is a RECORDING that every bake replays in full, so a canvas
|
|
85
|
+
// redrawn every frame without `reset()` gets slower with every update. `_bakeSurface` compares the
|
|
86
|
+
// buffer against the previous bake and warns once when it keeps growing past the threshold.
|
|
87
|
+
private _lastBakeLen = 0
|
|
88
|
+
private _warnedGrowth = false
|
|
79
89
|
|
|
80
90
|
constructor(width: number, height: number, opts: { pixelRatio?: number } = {}) {
|
|
81
91
|
this.width = width
|
|
@@ -198,6 +208,17 @@ export class Canvas {
|
|
|
198
208
|
|
|
199
209
|
/** @internal — rasterize the recorded commands into the surface; returns the surface id. */
|
|
200
210
|
_bakeSurface(): number {
|
|
211
|
+
const len = this._cmd.length
|
|
212
|
+
if (!this._warnedGrowth && len > CMD_GROWTH_WARN && this._lastBakeLen > 0 && len > this._lastBakeLen) {
|
|
213
|
+
this._warnedGrowth = true
|
|
214
|
+
console.warn(
|
|
215
|
+
`[canvas] ${this.width}×${this.height}: the command buffer keeps growing (${len} values, ` +
|
|
216
|
+
`${this._lastBakeLen} at the previous bake) — a Canvas records every call and each update() ` +
|
|
217
|
+
`replays all of it, so a redraw that never calls reset() gets slower every frame. ` +
|
|
218
|
+
`Call canvas.reset() before redrawing (or toBitmap() + reset() to flatten a drawing).`,
|
|
219
|
+
)
|
|
220
|
+
}
|
|
221
|
+
this._lastBakeLen = len
|
|
201
222
|
const buf = new Float32Array(this._cmd)
|
|
202
223
|
this._surfaceId = _creatorCanvas.rasterize(this._surfaceId, this.width, this.height, this.pixelRatio, buf, buf.length, this._refs)
|
|
203
224
|
return this._surfaceId
|
|
@@ -39,6 +39,17 @@ test("a 2D project (new Scene2D) is marked 2d: enable and gl: disable", async ()
|
|
|
39
39
|
expect(js).toContain("_creator2d.createScene")
|
|
40
40
|
})
|
|
41
41
|
|
|
42
|
+
test("a 3D project with a Canvas is NOT marked 2d: enable (Canvas keeps a `_creator2d.` texture path)", async () => {
|
|
43
|
+
const js = await compileProject({
|
|
44
|
+
name: "Car",
|
|
45
|
+
publicUrl: "https://le.codes",
|
|
46
|
+
entries: [ { path: "/main.ts", type: "text", text: "const c = new Canvas(64, 64)\nc.update()\nnew Scene().open()\n" } ],
|
|
47
|
+
})
|
|
48
|
+
expect(js).toContain("_creator2d.") // the canvas 2D-texture path survives tree-shaking
|
|
49
|
+
expect(js).not.toContain("// 2d: enable") // …but no Scene2D → the 2D engine is not in use
|
|
50
|
+
expect(js).not.toContain("// gl: disable")
|
|
51
|
+
})
|
|
52
|
+
|
|
42
53
|
test("2D and 3D bundles are DISJOINT (tree-shake drops the other engine)", async () => {
|
|
43
54
|
const twoD = await compileProject({
|
|
44
55
|
name: "2D", publicUrl: "https://le.codes", header: false,
|
|
@@ -69,21 +69,22 @@ export type CompileOptions = {
|
|
|
69
69
|
serverUrl?: string
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!entry) {
|
|
77
|
-
throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.")
|
|
78
|
-
}
|
|
72
|
+
/** The bundle + its map SEPARATELY (the platform deploy writes the map as a sibling `.js.map`; the
|
|
73
|
+
* preview inlines it) plus the entrypoint the compile used. `code` carries the metadata header
|
|
74
|
+
* unless `header: false`; `map` is already shifted past that header. */
|
|
75
|
+
export type CompileResult = { code: string, map: string | null, entry: string }
|
|
79
76
|
|
|
77
|
+
/** Turn compile entries into the bundler's module map — the per-kind rules of the deployable
|
|
78
|
+
* bundle (resource → URL export, shader → per-backend URL template, .svg → SvgSource, .json →
|
|
79
|
+
* data, code verbatim). Exported for callers that build a `libraries` map from entries. */
|
|
80
|
+
export const entriesToFiles = (entries: CompileEntry[], opts: { publicUrl: string, localAssets?: boolean }): Record<string, string> => {
|
|
80
81
|
const files: Record<string, string> = {}
|
|
81
82
|
for (const e of entries) {
|
|
82
83
|
if (e.type === "resource") {
|
|
83
84
|
if (opts.localAssets && !e.remote) {
|
|
84
85
|
// The host resolves the asset by name at runtime (reads the local file → systemId); the
|
|
85
86
|
// "id:" prefix is the host's local-resource URL scheme (parsed by the UI + 2D texture paths).
|
|
86
|
-
const name = e.path.split(/[
|
|
87
|
+
const name = e.path.split(/[\/]/).pop() ?? e.path
|
|
87
88
|
files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(name)}))`
|
|
88
89
|
} else {
|
|
89
90
|
files[e.path] = `export default "${opts.publicUrl}${e.fileSrc ?? ""}"`
|
|
@@ -98,7 +99,7 @@ export const compileProject = async (opts: CompileOptions): Promise<string> => {
|
|
|
98
99
|
// desktop host reports "desktop" on Windows and "opengl" on Linux, iOS "metal".
|
|
99
100
|
// Only entries the CLI actually compiled+staged carry `shadersLocal`; server artifacts
|
|
100
101
|
// (URLs) and un-staged ones keep the URL forms below.
|
|
101
|
-
const name = shaders[0].src.split(/[
|
|
102
|
+
const name = shaders[0].src.split(/[\/]/).pop() ?? ""
|
|
102
103
|
const [prefix, suffix = ".filamat"] = name.split("_opengl")
|
|
103
104
|
files[e.path] = `export default ("id:" + _creatorUtils.fetchLocal(${JSON.stringify(prefix + "_")} + _creator.backend + ${JSON.stringify(suffix)}))`
|
|
104
105
|
} else if (shaders.length > 0) {
|
|
@@ -117,16 +118,30 @@ export const compileProject = async (opts: CompileOptions): Promise<string> => {
|
|
|
117
118
|
files[e.path] = e.text ?? ""
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
return files
|
|
122
|
+
}
|
|
120
123
|
|
|
124
|
+
export const compileProjectWithMap = async (opts: CompileOptions): Promise<CompileResult> => {
|
|
125
|
+
// Client bundle: every *.server.ts becomes its RPC/channel stub (server code stays server-side).
|
|
126
|
+
const entries = stubServerEntries(opts.entries, opts.serverUrl ?? "")
|
|
127
|
+
const entry = opts.entryOverride ?? detectEntry(entries, scanModulesChisel).entry
|
|
128
|
+
if (!entry) {
|
|
129
|
+
throw new Error("Couldn't find an entrypoint (no .ts/.js project file — name yours main.ts). Pass entryOverride.")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const files = entriesToFiles(entries, opts)
|
|
121
133
|
const { code, map } = await bundleProjectWithMap(
|
|
122
134
|
entry, files, opts.format ?? "esm", opts.minify, opts.editor, opts.libraries, opts.sourcemap ?? true,
|
|
123
135
|
)
|
|
124
|
-
if (opts.header === false) {
|
|
125
|
-
return map ? `${code}\n${inlineSourceMapComment(map)}` : code
|
|
126
|
-
}
|
|
136
|
+
if (opts.header === false) return { code, map, entry }
|
|
127
137
|
const header = buildHeader(opts.name, code, { icon: opts.icon, fonts: opts.fontsHeader, icons: opts.iconsHeader })
|
|
128
|
-
|
|
129
|
-
// Shift the map down past the header (prepended before the code); the inline comment lands last.
|
|
138
|
+
// Shift the map down past the header (prepended before the code).
|
|
130
139
|
const headerLines = (header.match(/\n/g) ?? []).length
|
|
131
|
-
return
|
|
140
|
+
return { code: header + code, map: map ? offsetSourceMap(map, headerLines) : null, entry }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** The bundle as ONE string, the map inlined at its end (the shipped/preview form). */
|
|
144
|
+
export const compileProject = async (opts: CompileOptions): Promise<string> => {
|
|
145
|
+
const { code, map } = await compileProjectWithMap(opts)
|
|
146
|
+
return map ? `${code}\n${inlineSourceMapComment(map)}` : code
|
|
132
147
|
}
|
package/src/compile/header.ts
CHANGED
|
@@ -33,8 +33,11 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
|
|
|
33
33
|
else if (code.includes("_creator.createEngine")) header += `// gl: defer\n`
|
|
34
34
|
|
|
35
35
|
// 2D (creator-2d) — non-exclusive: emitted alongside `gl: disable` so hosts skip Filament and a
|
|
36
|
-
// 2D-aware host brings up the lightweight sokol engine instead.
|
|
37
|
-
|
|
36
|
+
// 2D-aware host brings up the lightweight sokol engine instead. Keyed on the scene constructor
|
|
37
|
+
// (like GL above), NOT on any `_creator2d.` reference: Canvas carries a 2D-texture path
|
|
38
|
+
// (`canvas.texture()` → `_creator2d.createTextureFromCanvas`) that chisel keeps in a pure-3D
|
|
39
|
+
// bundle, and nothing 2D works without a Scene2D anyway.
|
|
40
|
+
if (code.includes("_creator2d.createScene")) header += `// 2d: enable\n`
|
|
38
41
|
|
|
39
42
|
if (code.includes("_creator.createARController")) header += `// ar: enable\n`
|
|
40
43
|
|
|
@@ -61,7 +64,7 @@ export const buildHeader = (name: string, code: string, opts: HeaderOptions = {}
|
|
|
61
64
|
/** Same engine signals the header uses, for callers that need the project kind (e.g. icon type). */
|
|
62
65
|
export const detectProjectKind = (code: string): "ar" | "ui" | "2d" | "3d" => {
|
|
63
66
|
if (code.includes("_creator.createARController")) return "ar"
|
|
64
|
-
if (code.includes("_creator2d.")) return "2d"
|
|
67
|
+
if (code.includes("_creator2d.createScene")) return "2d"
|
|
65
68
|
if (!code.includes("_creator.")) return "ui"
|
|
66
69
|
return "3d"
|
|
67
70
|
}
|