lecodes-cli 0.18.2 → 0.19.2

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.
Files changed (224) hide show
  1. package/dist/index.js +1866 -301
  2. package/package.json +14 -12
  3. package/runtime/scene-harness.json +1 -1
  4. package/runtime/sdk-types.json +1 -1
  5. package/src/api.ts +302 -0
  6. package/src/browserAuth.ts +87 -0
  7. package/src/cmgenTool.ts +104 -0
  8. package/src/commands/app.ts +891 -0
  9. package/src/commands/appAndroid.ts +603 -0
  10. package/src/commands/appDesktop.ts +326 -0
  11. package/src/commands/appDesktopMac.ts +470 -0
  12. package/src/commands/appIcon.ts +187 -0
  13. package/src/commands/appShared.ts +448 -0
  14. package/src/commands/appTemplates.ts +883 -0
  15. package/src/commands/appTemplatesAndroid.ts +599 -0
  16. package/src/commands/appTemplatesGradlew.ts +9 -0
  17. package/src/commands/assets.ts +28 -0
  18. package/src/commands/clone.ts +60 -0
  19. package/src/commands/compile.ts +253 -0
  20. package/src/commands/create.ts +59 -0
  21. package/src/commands/design.ts +863 -0
  22. package/src/commands/designTemplates.ts +11 -0
  23. package/src/commands/desktop.ts +122 -0
  24. package/src/commands/dev.ts +214 -0
  25. package/src/commands/diff.ts +59 -0
  26. package/src/commands/init.ts +191 -0
  27. package/src/commands/install.ts +198 -0
  28. package/src/commands/lightmap.ts +290 -0
  29. package/src/commands/link.ts +147 -0
  30. package/src/commands/login.ts +24 -0
  31. package/src/commands/navmesh.ts +225 -0
  32. package/src/commands/pn.ts +305 -0
  33. package/src/commands/projectTemplates.ts +129 -0
  34. package/src/commands/pull.ts +109 -0
  35. package/src/commands/push.ts +173 -0
  36. package/src/commands/render.ts +414 -0
  37. package/src/commands/scene.ts +148 -0
  38. package/src/commands/shaders.ts +157 -0
  39. package/src/commands/status.ts +29 -0
  40. package/src/commands/test.ts +370 -0
  41. package/src/commands/thumbs.ts +178 -0
  42. package/src/commands/types.ts +87 -0
  43. package/src/commands/update.ts +190 -0
  44. package/src/compile/assetIcons.ts +214 -0
  45. package/src/compile/collect.ts +46 -0
  46. package/src/compile/collectLocal.ts +37 -0
  47. package/src/compile/designCompile.ts +104 -0
  48. package/src/compile/fonts.ts +156 -0
  49. package/src/compile/headlessBundle.ts +129 -0
  50. package/src/compile/nativeStack.ts +21 -0
  51. package/src/compile/projectCompile.ts +109 -0
  52. package/src/compile/sceneCompile.ts +113 -0
  53. package/src/compile/screenEntry.ts +127 -0
  54. package/src/compile/shaders.ts +245 -0
  55. package/src/config.ts +42 -0
  56. package/src/designMeta.ts +35 -0
  57. package/src/desktopRenderer.ts +532 -0
  58. package/src/desktopScript.ts +276 -0
  59. package/src/dev/clientTemplates.ts +160 -0
  60. package/src/dev/devServer.ts +292 -0
  61. package/src/dev/wsServer.ts +144 -0
  62. package/src/distRoot.ts +20 -0
  63. package/src/ignore.ts +163 -0
  64. package/src/index.ts +491 -0
  65. package/src/lecodes-3d-editor.d.ts +41 -0
  66. package/src/lecodes-assets.d.ts +7 -0
  67. package/src/lecodes-design.d.ts +191 -0
  68. package/src/lecodes-renderer.d.ts +131 -0
  69. package/src/localFiles.ts +144 -0
  70. package/src/manifest.ts +46 -0
  71. package/src/matcTool.ts +137 -0
  72. package/src/peers.ts +40 -0
  73. package/src/project.ts +61 -0
  74. package/src/projectEnv.ts +94 -0
  75. package/src/qrcode-terminal.d.ts +9 -0
  76. package/src/releases.ts +125 -0
  77. package/src/serverDiff.ts +78 -0
  78. package/src/textDiff.ts +103 -0
  79. package/src/types.ts +0 -0
  80. package/src/util.ts +146 -0
  81. package/runtime/sdk/animate/animate.ts +0 -238
  82. package/runtime/sdk/animate/bezier.ts +0 -138
  83. package/runtime/sdk/animate/easings.ts +0 -126
  84. package/runtime/sdk/canvas/Canvas.ts +0 -305
  85. package/runtime/sdk/core/Aspect.ts +0 -512
  86. package/runtime/sdk/core/InspectorUI.ts +0 -212
  87. package/runtime/sdk/core/color.ts +0 -66
  88. package/runtime/sdk/core/compWrite.ts +0 -42
  89. package/runtime/sdk/core/events.ts +0 -38
  90. package/runtime/sdk/core/fields.ts +0 -120
  91. package/runtime/sdk/core/registry.ts +0 -23
  92. package/runtime/sdk/core/signals.ts +0 -277
  93. package/runtime/sdk/core/time.ts +0 -81
  94. package/runtime/sdk/g2/Camera2D.ts +0 -40
  95. package/runtime/sdk/g2/CharacterController2D.ts +0 -276
  96. package/runtime/sdk/g2/Node2D.ts +0 -267
  97. package/runtime/sdk/g2/OneWay2D.ts +0 -66
  98. package/runtime/sdk/g2/Physics2D.ts +0 -346
  99. package/runtime/sdk/g2/Scene2D.ts +0 -209
  100. package/runtime/sdk/g2/Shape2D.ts +0 -259
  101. package/runtime/sdk/g2/Sprite.ts +0 -89
  102. package/runtime/sdk/g2/SpriteAnimation.ts +0 -171
  103. package/runtime/sdk/g2/SpriteSheet.ts +0 -166
  104. package/runtime/sdk/g2/Texture2D.ts +0 -47
  105. package/runtime/sdk/g2/Tilemap.ts +0 -41
  106. package/runtime/sdk/g2/Tileset.ts +0 -71
  107. package/runtime/sdk/g2/Trigger2D.ts +0 -77
  108. package/runtime/sdk/g2/autotile.ts +0 -433
  109. package/runtime/sdk/g2/cells.ts +0 -91
  110. package/runtime/sdk/g2/defineScene2d.ts +0 -381
  111. package/runtime/sdk/g2/groups2d.ts +0 -106
  112. package/runtime/sdk/g2/loop.ts +0 -50
  113. package/runtime/sdk/g2/scenarios2d.ts +0 -69
  114. package/runtime/sdk/g2/touch.ts +0 -83
  115. package/runtime/sdk/gl/Camera.ts +0 -160
  116. package/runtime/sdk/gl/CameraPlace.ts +0 -52
  117. package/runtime/sdk/gl/CharacterController.ts +0 -238
  118. package/runtime/sdk/gl/Gearbox.ts +0 -212
  119. package/runtime/sdk/gl/Geometry.ts +0 -279
  120. package/runtime/sdk/gl/IK.ts +0 -193
  121. package/runtime/sdk/gl/InstancedMesh.ts +0 -132
  122. package/runtime/sdk/gl/Light.ts +0 -99
  123. package/runtime/sdk/gl/Lightmap.ts +0 -179
  124. package/runtime/sdk/gl/Material.ts +0 -245
  125. package/runtime/sdk/gl/Mesh.ts +0 -83
  126. package/runtime/sdk/gl/Model.ts +0 -64
  127. package/runtime/sdk/gl/Node.ts +0 -350
  128. package/runtime/sdk/gl/Noise.ts +0 -30
  129. package/runtime/sdk/gl/Particles.ts +0 -676
  130. package/runtime/sdk/gl/Physics.ts +0 -222
  131. package/runtime/sdk/gl/Plane.ts +0 -53
  132. package/runtime/sdk/gl/Ray.ts +0 -16
  133. package/runtime/sdk/gl/Scene.ts +0 -479
  134. package/runtime/sdk/gl/Shape.ts +0 -377
  135. package/runtime/sdk/gl/Texture.ts +0 -46
  136. package/runtime/sdk/gl/Trigger.ts +0 -45
  137. package/runtime/sdk/gl/Vehicle.ts +0 -473
  138. package/runtime/sdk/gl/Wheel.ts +0 -240
  139. package/runtime/sdk/gl/animation/AnimationClip.ts +0 -204
  140. package/runtime/sdk/gl/animation/Animator.ts +0 -87
  141. package/runtime/sdk/gl/animation/Layer.ts +0 -29
  142. package/runtime/sdk/gl/animation/Loop.ts +0 -25
  143. package/runtime/sdk/gl/animation/Playback.ts +0 -43
  144. package/runtime/sdk/gl/animation/core.ts +0 -294
  145. package/runtime/sdk/gl/controls.ts +0 -95
  146. package/runtime/sdk/gl/physicsEvents.ts +0 -20
  147. package/runtime/sdk/gl/scenarios.ts +0 -291
  148. package/runtime/sdk/gl/state.ts +0 -6
  149. package/runtime/sdk/gl/touch.ts +0 -68
  150. package/runtime/sdk/inject.ts +0 -186
  151. package/runtime/sdk/math/Mathf.ts +0 -118
  152. package/runtime/sdk/math/mat4.ts +0 -278
  153. package/runtime/sdk/math/quat.ts +0 -232
  154. package/runtime/sdk/math/vec.ts +0 -255
  155. package/runtime/sdk/plugins/camera.ts +0 -81
  156. package/runtime/sdk/plugins/geolocation.ts +0 -123
  157. package/runtime/sdk/plugins/oauth.ts +0 -61
  158. package/runtime/sdk/plugins/permission.ts +0 -7
  159. package/runtime/sdk/plugins/push.ts +0 -132
  160. package/runtime/sdk/plugins/qr.ts +0 -73
  161. package/runtime/sdk/plugins/service.ts +0 -47
  162. package/runtime/sdk/runtime/app.ts +0 -101
  163. package/runtime/sdk/runtime/appEvents.ts +0 -54
  164. package/runtime/sdk/runtime/channel.ts +0 -61
  165. package/runtime/sdk/runtime/clipboard.ts +0 -20
  166. package/runtime/sdk/runtime/datetime.ts +0 -329
  167. package/runtime/sdk/runtime/device.ts +0 -293
  168. package/runtime/sdk/runtime/fetch.ts +0 -77
  169. package/runtime/sdk/runtime/files.ts +0 -23
  170. package/runtime/sdk/runtime/input.ts +0 -175
  171. package/runtime/sdk/runtime/media.ts +0 -111
  172. package/runtime/sdk/runtime/misc.ts +0 -16
  173. package/runtime/sdk/runtime/net.ts +0 -36
  174. package/runtime/sdk/runtime/rpc.ts +0 -218
  175. package/runtime/sdk/runtime/service.ts +0 -83
  176. package/runtime/sdk/runtime/share.ts +0 -9
  177. package/runtime/sdk/runtime/storage.ts +0 -13
  178. package/runtime/sdk/runtime/touch.ts +0 -76
  179. package/runtime/sdk/scene/defineScene.ts +0 -1227
  180. package/runtime/sdk/scene/editorPlugins.ts +0 -92
  181. package/runtime/sdk/scene/gizmos.ts +0 -148
  182. package/runtime/sdk/scene/grammar.ts +0 -120
  183. package/runtime/sdk/scene/material.ts +0 -188
  184. package/runtime/sdk/server/auth/appConfig.ts +0 -12
  185. package/runtime/sdk/server/auth/global.ts +0 -80
  186. package/runtime/sdk/server/auth/host.ts +0 -318
  187. package/runtime/sdk/server/auth/models.ts +0 -83
  188. package/runtime/sdk/server/auth/types.ts +0 -50
  189. package/runtime/sdk/server/channel.ts +0 -56
  190. package/runtime/sdk/server/context.ts +0 -36
  191. package/runtime/sdk/server/db/defineDb.ts +0 -237
  192. package/runtime/sdk/server/db/fields.ts +0 -132
  193. package/runtime/sdk/server/db/httpTransport.ts +0 -93
  194. package/runtime/sdk/server/db/index.ts +0 -7
  195. package/runtime/sdk/server/db/marci/query.ts +0 -412
  196. package/runtime/sdk/server/db/types.ts +0 -202
  197. package/runtime/sdk/server/errors.ts +0 -12
  198. package/runtime/sdk/server/host.ts +0 -74
  199. package/runtime/sdk/server/inject.ts +0 -13
  200. package/runtime/sdk/server/runtime.ts +0 -133
  201. package/runtime/sdk/server/validate.ts +0 -87
  202. package/runtime/sdk/ui/NativeView.ts +0 -142
  203. package/runtime/sdk/ui/UI.ts +0 -39
  204. package/runtime/sdk/ui/UIBottomSheet.ts +0 -139
  205. package/runtime/sdk/ui/UIButton.ts +0 -101
  206. package/runtime/sdk/ui/UIContainer.ts +0 -60
  207. package/runtime/sdk/ui/UIImage.ts +0 -83
  208. package/runtime/sdk/ui/UIInput.ts +0 -185
  209. package/runtime/sdk/ui/UIModal.ts +0 -139
  210. package/runtime/sdk/ui/UINode.ts +0 -826
  211. package/runtime/sdk/ui/UIPager.ts +0 -362
  212. package/runtime/sdk/ui/UIPopover.ts +0 -100
  213. package/runtime/sdk/ui/UIScreen.ts +0 -123
  214. package/runtime/sdk/ui/UIScrollable.ts +0 -87
  215. package/runtime/sdk/ui/UISpacer.ts +0 -14
  216. package/runtime/sdk/ui/UITabs.ts +0 -236
  217. package/runtime/sdk/ui/UIText.ts +0 -51
  218. package/runtime/sdk/ui/UIVideo.ts +0 -88
  219. package/runtime/sdk/ui/UIVirtualizedList.ts +0 -241
  220. package/runtime/sdk/ui/UIWidget.ts +0 -127
  221. package/runtime/sdk/ui/fonts.ts +0 -13
  222. package/runtime/sdk/ui/presentable.ts +0 -117
  223. package/runtime/sdk/ui/router.ts +0 -132
  224. package/runtime/sdk/ui/theme.ts +0 -84
@@ -1,209 +0,0 @@
1
- // A 2D scene: a draw set + an orthographic Camera2D. Opening a scene asks the host to bring up the
2
- // 2D engine and starts its RAF loop; only the active scene renders. Grown from the Phase 0 seed.
3
-
4
- import { Color, type ColorInput } from "../core/color"
5
- import { _installAspectFrames, _installTimeScale, _attachSystem, _detachSystem, _detachAllSystems, type Aspect, type AspectCtor, type FieldOf, type TargetOf } from "../core/Aspect"
6
- import { _bumpNavEpoch, _navSupported, _setCurrent, Presentable, type PresentOptions } from "../ui/presentable"
7
- import { cx, cy, type Vec2Like } from "../math/vec"
8
- import type { ClickEvent, TouchStartEvent } from "../runtime/touch"
9
- import { Camera2D } from "./Camera2D"
10
- import { node2dRegistry, type Node2D } from "./Node2D"
11
- import { ensurePointerEvents } from "./touch"
12
-
13
- // Route aspect update(dt) through the render-synced native phases (c2dRender early = before physics,
14
- // late = after animations / before draw), so followers/cameras are in lockstep with the draw. Done from
15
- // the 2D layer (not core) so a 3D-only bundle never pulls in the 2D bridge. Installed once, lazily on
16
- // the first Scene2D. On an older wasm without the hooks the installer returns false → setLoop fallback.
17
- let framesInstalled = false
18
- const installRenderSyncedFrames = (): void => {
19
- if (framesInstalled) return
20
- framesInstalled = true
21
- _installAspectFrames((early, late) => {
22
- if (typeof _creator2d.setEarlyUpdate !== "function") return false
23
- _creator2d.setEarlyUpdate(early)
24
- _creator2d.setLateUpdate(late)
25
- return true
26
- })
27
- // Time.scale / Time.paused reach the engine's physics + animation clocks (feature-detected).
28
- _installTimeScale((scale) => { _creator2d.setTimeScale?.(scale) })
29
- }
30
-
31
- export type Scene2DOptions = {
32
- /** Background color — '#10131a', 0x10131a, or [r,g,b]/[r,g,b,a] in 0..1. */
33
- background?: ColorInput
34
- /** Texture sampling for the 2D renderer: `'nearest'` (default — crisp pixel art) or `'linear'`
35
- * (smooth — for hi-res / non-pixel-art assets). Applied when the scene is opened, so switching
36
- * scenes restores the active scene's choice. NOTE: this is a GLOBAL renderer setting (one sampler
37
- * for ALL textures, not per-texture), so the currently-open scene's filter wins. Omit to leave the
38
- * current setting (engine default is nearest). */
39
- filter?: 'nearest' | 'linear'
40
- }
41
-
42
- /** Returned by scene2d.layer(n); chain .ySort() to enable top-down depth sorting for that layer. */
43
- class LayerHandle {
44
- private readonly sceneId: number
45
- private readonly layer: number
46
- constructor(sceneId: number, layer: number) { this.sceneId = sceneId; this.layer = layer }
47
- /** In this layer, lower-on-screen sprites overlap higher ones (top-down feet sorting). */
48
- ySort(enabled = true): this {
49
- _creator2d.setLayerYSort(this.sceneId, this.layer, enabled)
50
- return this
51
- }
52
- }
53
-
54
- export class Scene2D implements Presentable {
55
- /** Native scene handle. */
56
- readonly id: number
57
- readonly camera: Camera2D
58
-
59
- private static _active: Scene2D | null = null
60
- static get active(): Scene2D | null { return Scene2D._active }
61
-
62
- /** @internal — scene-level pointer listeners (the catch-all; target = hit node or null). */
63
- readonly _clickListeners: Array<(ev: ClickEvent<Node2D | null>) => void> = []
64
- readonly _touchStartListeners: Array<(ev: TouchStartEvent<Node2D | null>) => void> = []
65
-
66
- /** Texture filter to (re)assert on open(); the sampler is global, so each scene reclaims it. */
67
- private readonly _filter?: 'nearest' | 'linear'
68
-
69
- constructor(options: Scene2DOptions = {}) {
70
- installRenderSyncedFrames()
71
- this.id = _creator2d.createScene()
72
- this.camera = new Camera2D(this.id)
73
- this._filter = options.filter
74
- if (options.background !== undefined) this.background = options.background
75
- }
76
-
77
- set background(color: ColorInput) {
78
- const [ r, g, b, a ] = Color.toRgba01(color)
79
- _creator2d.sceneSetClearColor(this.id, r, g, b, a)
80
- }
81
-
82
- /** Address a draw layer to configure it, e.g. scene2d.layer(1).ySort(). */
83
- layer(n: number): LayerHandle {
84
- return new LayerHandle(this.id, n)
85
- }
86
-
87
- /** Topmost visible sprite whose world bounds contain the world point, or null. Body-free
88
- * hit-test for tap/click picking — pair with camera.screenToWorld(). */
89
- pick(worldPoint: Vec2Like): Node2D | null {
90
- const id = _creator2d.pickSprite(this.id, cx(worldPoint), cy(worldPoint))
91
- return id ? node2dRegistry.get(id) ?? null : null
92
- }
93
-
94
- /** Scene-level pointer events. The handler fires for every tap/press in the scene, with
95
- * `ev.target` = the hit Physics2D node (or null). On 'touchstart' you can ev.track(...) a drag. */
96
- addEventListener(channel: "click", callback: (ev: ClickEvent<Node2D | null>) => void): void
97
- addEventListener(channel: "touchstart", callback: (ev: TouchStartEvent<Node2D | null>) => void): void
98
- addEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
99
- const list = channel === "click" ? this._clickListeners : this._touchStartListeners
100
- if (!list.includes(callback)) { list.push(callback); ensurePointerEvents() }
101
- }
102
- removeEventListener(channel: "click" | "touchstart", callback: (ev: any) => void): void {
103
- const list = channel === "click" ? this._clickListeners : this._touchStartListeners
104
- const i = list.indexOf(callback)
105
- if (i >= 0) list.splice(i, 1)
106
- }
107
-
108
- add(...nodes: Node2D[]): this {
109
- for (const n of nodes) _creator2d.addEntityToScene(this.id, n.id)
110
- return this
111
- }
112
- remove(...nodes: Node2D[]): this {
113
- for (const n of nodes) _creator2d.removeEntityFromScene(this.id, n.id)
114
- return this
115
- }
116
-
117
- // ---- systems: aspects of the scene (core/Aspect.ts `System<'x', Scene2D>`) ----
118
- /** @internal class → instance, the authoritative store (named accessors mirror this). */
119
- readonly _systems = new Map<Function, Aspect<any, any, any>>()
120
-
121
- /** Attach (and configure) a system, or reconfigure it if already present. Returns the scene typed
122
- * as now-having it (`scene.system(Hud).hud`). A 3D `System<'x'>` (target Scene) is rejected here. */
123
- // `this: Self` (not `Self & Scene2D`): with the intersection TS infers Self = Scene2D and a chain
124
- // `scene.system(A).system(B)` loses A's accessor from the result type.
125
- system<Self extends TargetOf<A>, A extends Aspect<any, any, any>>(
126
- this: Self,
127
- ctor: AspectCtor<A>,
128
- opts?: Partial<A>,
129
- ): Self & FieldOf<A> {
130
- return _attachSystem(this as unknown as Scene2D, ctor, opts) as unknown as Self & FieldOf<A>
131
- }
132
- /** Safe access — undefined if the system isn't attached. */
133
- get<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): A | undefined {
134
- return this._systems.get(ctor) as A | undefined
135
- }
136
- /** Existence check AND type guard: inside `if (scene.has(Hud))`, `scene.hud` is present. */
137
- has<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this is this & FieldOf<A> {
138
- return this._systems.has(ctor)
139
- }
140
- /** Detach a system (runs its onDetach). Chainable. */
141
- removeSystem<A extends Aspect<any, any, any>>(ctor: AspectCtor<A>): this {
142
- _detachSystem(this, ctor)
143
- return this
144
- }
145
-
146
- // ---- Presentable (docs/navigation-presentable-plan.md) ----
147
-
148
- /** @internal presentation lifecycle — fired by the host on present/dismiss (new navigation),
149
- * or synchronously by open()/close() on legacy hosts. */
150
- readonly ol: (() => void)[] = []
151
- readonly cl: (() => void)[] = []
152
- _backButtonCallback?: () => void
153
- private _vd?: object
154
-
155
- /** Fires when the scene becomes the visible destination (incl. a pop revealing it). */
156
- onOpen(callback: () => void): this {
157
- this.ol.push(callback)
158
- return this
159
- }
160
- /** Fires when the scene stops being visible (closed, replaced, or covered by a push). */
161
- onClose(callback: () => void): this {
162
- this.cl.push(callback)
163
- return this
164
- }
165
- onBackPressed(callback: () => void): this {
166
- this._backButtonCallback = callback
167
- return this
168
- }
169
-
170
- /** @internal stable wire descriptor; hosts hand it back via router onChange (`_p` = this). */
171
- _viewDesc(): object {
172
- return this._vd ??= { type: "scene2d", sceneId: this.id, _p: this }
173
- }
174
-
175
- /** Make this the active scene — shows it as the current destination (replaces a screen /
176
- * another scene; only the active scene renders). */
177
- open(options?: PresentOptions): this {
178
- _bumpNavEpoch()
179
- // Reassert this scene's texture filter (the sampler is global — see Scene2DOptions.filter).
180
- if (this._filter !== undefined) _creator2d.setDefaultFilter(this._filter === 'linear')
181
- if (_navSupported()) {
182
- // The host activates the engine (openScene) as part of presenting the destination.
183
- _creatorUI.openView!(this._viewDesc(), options?.transition ?? "none")
184
- } else {
185
- _creator2d.openScene(this.id)
186
- for (const cb of this.ol) cb()
187
- }
188
- Scene2D._active = this
189
- _setCurrent(this)
190
- return this
191
- }
192
- close(): this {
193
- _bumpNavEpoch()
194
- if (_navSupported()) {
195
- _creatorUI.closeView!()
196
- } else {
197
- _creator2d.closeScene()
198
- for (const cb of this.cl) cb()
199
- }
200
- if (Scene2D._active === this) Scene2D._active = null
201
- if (Presentable.current === this) _setCurrent(null)
202
- return this
203
- }
204
- destroy(): void {
205
- _detachAllSystems(this) // systems go first (last-attached first), while the scene still exists
206
- this.close()
207
- _creator2d.destroyScene(this.id)
208
- }
209
- }
@@ -1,259 +0,0 @@
1
- // A collider shape, as an aspect on a Node2D. PURE GEOMETRY — attaching a Shape2D does not by itself
2
- // make the node rigid or a sensor; pair it with a Physics2D (rigid body) or a Trigger2D (sensor), both
3
- // of which require a Shape2D and turn its geometry into a Box2D fixture. The shape is also what pointer
4
- // picking hits, so a node is clickable once it has a Shape2D + Physics2D/Trigger2D (a body to query).
5
- //
6
- // wall.aspect(Shape2D, { box: [200, 16] }).aspect(Physics2D, { motion: 'static' })
7
- // goal.aspect(Shape2D, { circle: 20 }).aspect(Trigger2D)
8
- // hero.aspect(Shape2D, { capsule: { from: [0, 6], to: [0, 26], radius: 6 } }).aspect(Physics2D, { motion: 'dynamic' })
9
- //
10
- // With no geometry set, Shape2D derives a box from WHAT THE NODE DRAWS — the sprite's size scaled by
11
- // the node, positioned by its anchor. So `aspect(Shape2D, {})` on a sprite anchored at its feet gives
12
- // a collider that wraps the art instead of one centred on the pivot (half of it underground), which
13
- // is why every character used to carry a hand-computed offset.
14
- //
15
- // RE-CONFIGURING REBUILDS IT IN PLACE: `node.aspect(Shape2D, { capsule: … })` on a live node swaps the
16
- // fixture while the body keeps its id, its velocity and its place in the world. That is what makes a
17
- // crouch (and a live editor edit) possible.
18
-
19
- import { Aspect } from "../core/Aspect"
20
- import { Vec2, cx, cy, type Vec2Like } from "../math/vec"
21
- import { ALL_GROUPS } from "./groups2d"
22
- import type { Node2D } from "./Node2D"
23
-
24
- /** @internal How an owner (Physics2D / Trigger2D) wants the geometry realised. */
25
- export type ShapeFixture2D = {
26
- sensor: boolean
27
- friction: number
28
- bounce: number
29
- category?: number
30
- mask?: number
31
- /** Chains carry no mass, so Box2D only accepts them on a static body. */
32
- staticBody?: boolean
33
- }
34
-
35
- export class Shape2D extends Aspect<"shape", Node2D> {
36
- static readonly aspect = "shape"
37
-
38
- /** Box half-extents [hw, hh] (world units). */
39
- box?: Vec2Like
40
- /** Circle radius. */
41
- circle?: number
42
- /** Capsule between two local points, with a radius. */
43
- capsule?: { from: Vec2Like, to: Vec2Like, radius: number }
44
- /** Thin segment (an edge) between two local points. */
45
- segment?: { from: Vec2Like, to: Vec2Like }
46
- /** Convex polygon, up to 8 local points (a convex hull is computed — a CONCAVE outline silently
47
- * becomes its hull; use `chain` for those). */
48
- polygon?: Vec2Like[]
49
- /**
50
- * A polyline of connected segments — the one shape that expresses a long CONCAVE surface (rolling
51
- * terrain, a cave wall) in one piece, seam-free. Pass only the points that should be solid; the
52
- * tangent points Box2D needs at the ends are synthesised here.
53
- *
54
- * STATIC bodies only (a polyline has no area, so no mass), at least 2 points (4 with `loop`), and it
55
- * must not self-intersect. It is ONE-SIDED: solid on the right of the point order — walk the points
56
- * so that the solid material is on your right, or set `flip`.
57
- */
58
- chain?: Vec2Like[]
59
- /** `chain`: close the contour (an island / a room boundary). Needs at least 4 points. */
60
- loop?: boolean
61
- /** `chain`: put the solid side on the other side of the line. */
62
- flip?: boolean
63
- /** The collider's centre relative to the node, in world units. Applies to EVERY kind — it
64
- * translates a capsule's / segment's / polygon's / chain's points too, unlike the old `offset`. */
65
- origin?: Vec2Like
66
-
67
- /** @internal Geometry as of the last build — an unchanged signature means nothing to rebuild. */
68
- private _sig = ""
69
- /** @internal The last resize the CHARACTER refused (backs `controller.resizing`). */
70
- _resizeRefused = false
71
- /** @internal How the owner asked for it, so a rebuild can replay the same fixture. */
72
- private _fx: ShapeFixture2D | null = null
73
- private _handle = 0
74
-
75
- /** @internal — the character claimed this shape; record the geometry so a later re-configure is
76
- * compared against it. */
77
- _claimedByCharacter(): void { this._sig = this._signature(); this._resizeRefused = false }
78
-
79
- /** @internal — realise this geometry as a fixture on `handle` (a body from Physics2D/Trigger2D). */
80
- _addTo(handle: number, fx: ShapeFixture2D): void {
81
- this._handle = handle
82
- this._fx = fx
83
- this._sig = this._signature()
84
- this._create()
85
- }
86
-
87
- /** @internal — the owner's material changed live; keep it for the next rebuild. */
88
- _setMaterial(friction: number, bounce: number): void {
89
- if (this._fx) { this._fx.friction = friction; this._fx.bounce = bounce }
90
- }
91
-
92
- /** @internal — the owner's filter changed live. */
93
- _setFilter(category: number, mask: number): void {
94
- if (this._fx) { this._fx.category = category; this._fx.mask = mask }
95
- }
96
-
97
- onDetach(): void { this._handle = 0; this._fx = null; this._sig = ""; this._resizeRefused = false }
98
-
99
- /**
100
- * Re-configuring the aspect swaps the fixture in place — same body, same id, same velocity, same
101
- * position. Nothing happens when the geometry is unchanged, so re-applying the same options in a
102
- * loop is free.
103
- */
104
- onReconfigure(): void {
105
- const sig = this._signature()
106
- if (sig === this._sig) return
107
-
108
- // A CharacterController2D owns this shape instead of a body: resizing goes to the mover, which
109
- // VALIDATES it (you cannot stand up under a low pipe) and can refuse.
110
- if (this.node._xfKind === 1) {
111
- const cap = this._capsule()
112
- const ok = _creator2d.characterSetCapsule(this.node._xf, cap.x1, cap.y1, cap.x2, cap.y2, cap.radius)
113
- // On refusal NOTHING changed — so DON'T commit the signature. The next identical
114
- // `aspect(Shape2D, …)` then still sees a difference and retries; committing here would swallow
115
- // every retry for good, which is the whole trap in this design.
116
- this._resizeRefused = !ok
117
- if (ok) this._sig = sig
118
- return
119
- }
120
-
121
- if (!this._handle || !this._fx) return
122
- this._sig = sig
123
- _creator2d.physicsClearShapes(this._handle)
124
- this._create()
125
- }
126
-
127
- /**
128
- * @internal — this geometry as a CAPSULE, for CharacterController2D (the mover takes nothing else).
129
- * An explicit capsule is used as given; anything else is derived from the drawn rect, so
130
- * `aspect(Shape2D, {})` on a sprite gives a capsule that wraps the art.
131
- */
132
- _capsule(): { x1: number, y1: number, x2: number, y2: number, radius: number } {
133
- const ox = this.origin ? cx(this.origin) : 0
134
- const oy = this.origin ? cy(this.origin) : 0
135
- if (this.capsule) {
136
- const { from, to, radius } = this.capsule
137
- return { x1: cx(from) + ox, y1: cy(from) + oy, x2: cx(to) + ox, y2: cy(to) + oy, radius }
138
- }
139
- if (this.circle !== undefined) return { x1: ox, y1: oy, x2: ox, y2: oy, radius: this.circle }
140
- let hw = 8, hh = 8, bx = ox, by = oy
141
- if (this.box) { hw = cx(this.box); hh = cy(this.box) }
142
- else {
143
- const a = this._autoBox()
144
- hw = a.hw; hh = a.hh
145
- if (!this.origin) { bx = a.x; by = a.y }
146
- }
147
- const radius = Math.min(hw, hh)
148
- const half = Math.max(0, hh - radius)
149
- return { x1: bx, y1: by - half, x2: bx, y2: by + half, radius }
150
- }
151
-
152
- // ---- internals ---------------------------------------------------------------------------------
153
-
154
- private _create(): void {
155
- const fx = this._fx!
156
- const handle = this._handle
157
- const category = fx.category ?? ALL_GROUPS
158
- const mask = fx.mask ?? ALL_GROUPS
159
- const ox = this.origin ? cx(this.origin) : 0
160
- const oy = this.origin ? cy(this.origin) : 0
161
-
162
- if (this.chain) {
163
- this._createChain(handle, fx, category, mask, ox, oy)
164
- } else if (this.circle !== undefined) {
165
- _creator2d.physicsAddCircle(handle, this.circle, ox, oy, fx.friction, fx.bounce, fx.sensor, category, mask)
166
- } else if (this.capsule) {
167
- const { from, to, radius } = this.capsule
168
- _creator2d.physicsAddCapsule(handle, cx(from) + ox, cy(from) + oy, cx(to) + ox, cy(to) + oy, radius,
169
- fx.friction, fx.bounce, fx.sensor, category, mask)
170
- } else if (this.segment) {
171
- const { from, to } = this.segment
172
- _creator2d.physicsAddSegment(handle, cx(from) + ox, cy(from) + oy, cx(to) + ox, cy(to) + oy,
173
- fx.friction, fx.bounce, category, mask)
174
- } else if (this.polygon) {
175
- const pts = new Float32Array(this.polygon.length * 2)
176
- this.polygon.forEach((p, i) => { pts[i * 2] = cx(p) + ox; pts[i * 2 + 1] = cy(p) + oy })
177
- _creator2d.physicsAddPolygon(handle, pts, this.polygon.length, fx.friction, fx.bounce, fx.sensor, category, mask)
178
- } else {
179
- // box: explicit half-extents, or "auto" — the rect the node actually draws.
180
- let hw = 8, hh = 8, bx = ox, by = oy
181
- if (this.box) {
182
- hw = cx(this.box); hh = cy(this.box)
183
- } else {
184
- const auto = this._autoBox()
185
- hw = auto.hw; hh = auto.hh
186
- if (!this.origin) { bx = auto.x; by = auto.y } // an explicit origin still wins
187
- }
188
- _creator2d.physicsAddBox(handle, hw, hh, bx, by, fx.friction, fx.bounce, fx.sensor, category, mask)
189
- }
190
- }
191
-
192
- /**
193
- * Box2D's chain wants the two TANGENT points as extra entries and does not collide on its first or
194
- * last edge — pass it your outline verbatim and you lose both end segments (four points give ONE).
195
- * So the caller passes only the solid points and the ends are extrapolated here: `p0 + (p0 - p1)`.
196
- * A loop needs none of that (every point is interior) but does need 4 of them.
197
- */
198
- private _createChain(handle: number, fx: ShapeFixture2D, category: number, mask: number, ox: number, oy: number): void {
199
- const src = this.chain!
200
- const loop = this.loop === true
201
- if (src.length < (loop ? 4 : 2)) {
202
- throw new Error(`Shape2D chain needs at least ${loop ? 4 : 2} points (got ${src.length})${loop ? " — a closed contour of 3 is a polygon" : ""}`)
203
- }
204
- if (fx.staticBody === false) {
205
- throw new Error("Shape2D { chain } works on a STATIC body only — a polyline has no area, so no mass. Use polygon/box for a dynamic body.")
206
- }
207
- const order = this.flip === true ? [ ...src ].reverse() : src
208
- const p: number[] = []
209
- if (!loop) {
210
- // leading ghost: mirror the first point across the second
211
- p.push(2 * cx(order[0]) - cx(order[1]) + ox, 2 * cy(order[0]) - cy(order[1]) + oy)
212
- }
213
- for (const v of order) p.push(cx(v) + ox, cy(v) + oy)
214
- if (!loop) {
215
- const n = order.length
216
- p.push(2 * cx(order[n - 1]) - cx(order[n - 2]) + ox, 2 * cy(order[n - 1]) - cy(order[n - 2]) + oy)
217
- }
218
- _creator2d.physicsAddChain(handle, new Float32Array(p), p.length / 2, loop, fx.friction, fx.bounce, category, mask)
219
- }
220
-
221
- // Everything that changes the GEOMETRY, so a re-configure that only touches the material is free.
222
- private _signature(): string {
223
- const o = this.origin ? `${cx(this.origin)},${cy(this.origin)}` : ""
224
- if (this.chain) return `chain:${this.chain.map(v => `${cx(v)},${cy(v)}`).join(";")}|${this.loop}|${this.flip}|${o}`
225
- if (this.circle !== undefined) return `circle:${this.circle}|${o}`
226
- if (this.capsule) {
227
- const { from, to, radius } = this.capsule
228
- return `cap:${cx(from)},${cy(from)},${cx(to)},${cy(to)},${radius}|${o}`
229
- }
230
- if (this.segment) {
231
- const { from, to } = this.segment
232
- return `seg:${cx(from)},${cy(from)},${cx(to)},${cy(to)}|${o}`
233
- }
234
- if (this.polygon) return `poly:${this.polygon.map(v => `${cx(v)},${cy(v)}`).join(";")}|${o}`
235
- if (this.box) return `box:${cx(this.box)},${cy(this.box)}|${o}`
236
- const a = this._autoBox()
237
- return `auto:${a.hw},${a.hh},${a.x},${a.y}|${o}`
238
- }
239
-
240
- /**
241
- * @internal — half-extents and centre of the rect this node DRAWS, in the node's own frame.
242
- *
243
- * Sprite anchor space is Y-DOWN while the world is Y-up (see docs/conventions.md), so `anchor: [.5, 1]`
244
- * ("feet") puts the art ABOVE the origin: its centre sits at +h/2. A node with no sprite falls back
245
- * to 8x8 at the origin. This is a SNAPSHOT taken when the fixture is built — growing the sprite later
246
- * does not resize the collider; re-configure the aspect to re-measure.
247
- */
248
- private _autoBox(): { hw: number, hh: number, x: number, y: number } {
249
- const n = this.node as { size?: Vec2, anchor?: Vec2, scale?: Vec2 }
250
- const size = n.size
251
- if (!size || !(size.x > 0) || !(size.y > 0)) return { hw: 8, hh: 8, x: 0, y: 0 }
252
- const sx = n.scale ? Math.abs(n.scale.x) : 1
253
- const sy = n.scale ? Math.abs(n.scale.y) : 1
254
- const w = size.x * sx, h = size.y * sy
255
- const ax = n.anchor ? n.anchor.x : 0.5
256
- const ay = n.anchor ? n.anchor.y : 0.5
257
- return { hw: w * 0.5, hh: h * 0.5, x: (0.5 - ax) * w, y: (ay - 0.5) * h }
258
- }
259
- }
@@ -1,89 +0,0 @@
1
- // A textured 2D node. Cheap setters proxy straight to the native sprite; the data lives in
2
- // creator-2d. Sprite-sheet animation is no longer built in — attach the SpriteAnimation aspect:
3
- // new Sprite({ texture }).aspect(SpriteAnimation, { size: [32, 48], idle: [0,1,2,3] }).anim.play('idle')
4
-
5
- import { Color, type ColorInput } from "../core/color"
6
- import { Vec2, type Vec2Like } from "../math/vec"
7
- import { Canvas, isCanvas } from "../canvas/Canvas"
8
- import { Node2D } from "./Node2D"
9
- import type { Texture2D } from "./Texture2D"
10
-
11
- /** Anything that can back a sprite: a texture, or a Canvas (baked to a texture on assign). */
12
- export type TextureSource = Texture2D | Canvas
13
-
14
- export type SpriteOptions = {
15
- texture?: TextureSource
16
- /** Pivot, [0,0] = top-left .. [1,1] = bottom-right. Use [0.5, 1] (feet) for Y-sorted depth. */
17
- anchor?: Vec2Like
18
- /** World size; defaults to the texture's pixel dimensions. */
19
- size?: Vec2Like
20
- /** Normalized UV sub-rect [u0,v0,u1,v1]. */
21
- frame?: [number, number, number, number]
22
- /** Tint multiplied with the texture. */
23
- color?: ColorInput
24
- opacity?: number
25
- layer?: number
26
- position?: Vec2Like
27
- }
28
-
29
- export class Sprite extends Node2D {
30
- private _texture?: Texture2D
31
- private _anchor = new Vec2(0.5, 0.5)
32
- private _color: ColorInput = 0xffffff
33
- private _opacity = 1
34
- private _flipX = false
35
- private _flipY = false
36
- private _size: Vec2 | null = null
37
-
38
- constructor(options: SpriteOptions = {}) {
39
- super()
40
- if (options.texture) this.texture = options.texture
41
- if (options.size) this.size = options.size
42
- if (options.anchor) this.anchor = options.anchor
43
- if (options.frame) this.frame = options.frame
44
- if (options.color !== undefined) this.color = options.color
45
- if (options.opacity !== undefined) this.opacity = options.opacity
46
- if (options.layer !== undefined) this.layer = options.layer
47
- if (options.position) this.position = options.position
48
- }
49
-
50
- get texture(): Texture2D | undefined { return this._texture }
51
- set texture(t: TextureSource | undefined) {
52
- const tex = isCanvas(t) ? t.texture() : t // a Canvas bakes to a texture on assign
53
- this._texture = tex
54
- if (!tex) return
55
- _creator2d.setSprite(this.id, tex.id) // native resets UV + size to the full (pixel) texture
56
- // A Canvas is authored in LOGICAL units but baked at logical × pixelRatio, so default the sprite's
57
- // WORLD size to the canvas's LOGICAL size — pixelRatio then only affects crispness, not on-screen
58
- // size. (A plain texture keeps the native default: the texture's pixel dimensions.)
59
- if (!this._size && isCanvas(t)) this._size = new Vec2(t.width, t.height)
60
- if (this._size) _creator2d.setSpriteSize(this.id, this._size.x, this._size.y)
61
- }
62
-
63
- /** Current world size: explicit override if set, else the texture's pixel dimensions (a Canvas-backed
64
- * sprite defaults to the canvas's LOGICAL size, so pixelRatio is crispness only, not size). */
65
- get size(): Vec2 { return this._size ?? (this._texture ? new Vec2(this._texture.width, this._texture.height) : new Vec2(0, 0)) }
66
- set size(v: Vec2Like) { this._size = new Vec2(v); _creator2d.setSpriteSize(this.id, this._size.x, this._size.y) }
67
-
68
- get anchor(): Vec2 { return this._anchor.clone() }
69
- set anchor(v: Vec2Like) { this._anchor = new Vec2(v); _creator2d.setSpriteAnchor(this.id, this._anchor.x, this._anchor.y) }
70
-
71
- /** Set the UV sub-rect (normalized). */
72
- set frame(uv: [number, number, number, number]) { _creator2d.setSpriteFrame(this.id, uv[0], uv[1], uv[2], uv[3]) }
73
- /** Set the sub-rect in texture pixels. */
74
- setFramePx(px: number, py: number, pw: number, ph: number): this {
75
- _creator2d.setSpriteFramePx(this.id, px, py, pw, ph)
76
- return this
77
- }
78
-
79
- get color(): ColorInput { return this._color }
80
- set color(c: ColorInput) { this._color = c; const [ r, g, b ] = Color.toRgb01(c); _creator2d.setSpriteColor(this.id, r, g, b) }
81
-
82
- get opacity(): number { return this._opacity }
83
- set opacity(a: number) { this._opacity = a; _creator2d.setSpriteOpacity(this.id, a) }
84
-
85
- get flipX(): boolean { return this._flipX }
86
- set flipX(v: boolean) { this._flipX = v; _creator2d.setSpriteFlip(this.id, v, this._flipY) }
87
- get flipY(): boolean { return this._flipY }
88
- set flipY(v: boolean) { this._flipY = v; _creator2d.setSpriteFlip(this.id, this._flipX, v) }
89
- }