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,92 +0,0 @@
1
- // Editor plugins: `registerEditorWindow` / `registerEditorTool` (docs/scene-editor-plan.md,
2
- // phase 11). Registrations live in `*.editor.ts` files — compiled and executed ONLY in
3
- // scene-editor bundles (phase 10), so none of this reaches shipped app.js (nothing references it
4
- // there, and method-granular DCE drops it).
5
- //
6
- // The scene-editor harness (packages/projects/src/scene-editor) reads the registry through the
7
- // injected `__editorPlugins` global — the harness, the scene, and the editor files compile into
8
- // ONE bundle, so they share this module instance. Windows and tools describe their UI through the
9
- // same immediate-mode `InspectorUI` protocol custom inspector cards use (one widget vocabulary,
10
- // three mount points), and mutate the scene ONLY through the `editor` doc API — every plugin write
11
- // lands in the scene document (undoable, diffable), never in live engine state.
12
-
13
- import type { InspectorUI } from "../core/InspectorUI"
14
-
15
- /** A geometry hit under the viewport pointer, handed to tools. Physics builds raycast the scene's
16
- * meshes (precise); otherwise (and on misses) the hit falls back to the ground plane (y = 0). */
17
- export type EditorRayHit = {
18
- point: [number, number, number]
19
- normal: [number, number, number]
20
- /** Absolute path of the scene-file node the hit belongs to ("city/in1/pt1") — null for
21
- * ground-plane fallback hits. */
22
- node: string | null
23
- }
24
-
25
- /**
26
- * The editor scripting API handed to windows and tools. Nodes are addressed by ABSOLUTE PATH —
27
- * '/'-joined names from the scene root; a root node's path is its bare name. Doc-op methods write
28
- * the scene DOCUMENT through the editor's normal commit path (undo, file, live patching all
29
- * included); they return false / no-op when the document can't take the edit (sibling-name
30
- * collision, unknown path).
31
- */
32
- export type EditorApi = {
33
- /** The currently selected node path (or `path::part` key for asset internals), null when
34
- * nothing is selected. */
35
- readonly selection: string | null
36
- select(path: string | null): void
37
- /** The scene document's nodes: absolute path, sibling-unique display name, source kind
38
- * (mesh / model / light / group / …). */
39
- nodes(): { path: string, name: string, kind: string }[]
40
- /** First unused "base", "base2", "base3", … name among the SIBLINGS under `parentPath`
41
- * (scene root when omitted). */
42
- uniqueName(base: string, parentPath?: string): string
43
- /** Add a ROOT node from plain def data (scene-file grammar; a string `model` value means an
44
- * asset path). `name` may not contain '/' or ':'. One undo step unless grouped by `transact`. */
45
- addNode(name: string, def: Record<string, unknown>): boolean
46
- /** Write one def prop (transforms apply live; anything else patches/re-runs the node). */
47
- setProp(path: string, key: string, value: unknown): boolean
48
- removeNode(path: string): void
49
- /** Duplicate a node; returns the copy's path (null when it can't). */
50
- duplicate(path: string): string | null
51
- /** Raycast the scene under a viewport pixel (same hit rules as tool clicks). */
52
- raycast(screenX: number, screenY: number): EditorRayHit | null
53
- /** Group every doc edit inside `fn` into ONE undo step. */
54
- transact(fn: () => void): void
55
- }
56
-
57
- export type EditorWindowFn = (ui: InspectorUI, editor: EditorApi) => void
58
-
59
- export type EditorToolHooks = {
60
- /** CSS cursor for the viewport while this tool is active (default "crosshair"). */
61
- cursor?: string
62
- /** A short glyph (one character / emoji) for the tool's toolbar button — tools without one all
63
- * share the generic wand icon, so any editor with two or more tools should set it. */
64
- icon?: string
65
- /** A viewport click while the tool is active — `hit` is the raycast result under the pointer.
66
- * Viewport clicks route to the tool while it is active. */
67
- onViewportClick?(hit: EditorRayHit, editor: EditorApi): void
68
- }
69
-
70
- /** @internal The registry the scene-editor harness reads (windows/tools in registration order). */
71
- export const __editorPlugins = {
72
- windows: [] as { title: string, render: EditorWindowFn }[],
73
- tools: [] as { name: string, hooks: EditorToolHooks }[],
74
- }
75
-
76
- /** Register an editor panel — a collapsible overlay docked over the viewport (the inspector rail
77
- * stays selection-scoped). A window that emits `ui.toolButton` for a tool becomes that tool's
78
- * settings panel: activating the tool expands and highlights it. `render` re-runs immediate-mode
79
- * on every interaction — same protocol as `static inspector` cards. */
80
- export const registerEditorWindow = (title: string, render: EditorWindowFn): void => {
81
- const existing = __editorPlugins.windows.find((w) => w.title === title)
82
- if (existing) existing.render = render
83
- else __editorPlugins.windows.push({ title, render })
84
- }
85
-
86
- /** Register a viewport tool — a toolbar entry beside move/rotate/scale (activate it there or via
87
- * `ui.toolButton(label, name)`). While active, viewport clicks arrive as raycast hits. */
88
- export const registerEditorTool = (name: string, hooks: EditorToolHooks): void => {
89
- const existing = __editorPlugins.tools.find((t) => t.name === name)
90
- if (existing) existing.hooks = hooks
91
- else __editorPlugins.tools.push({ name, hooks })
92
- }
@@ -1,148 +0,0 @@
1
- // Editor gizmos: immediate-mode line drawing for code that runs while a scene is EDITED — a
2
- // generator aspect's `rebuild()`, a `make()` factory, an editor tool's hooks. Nothing here touches
3
- // the scene: calls accumulate WORLD-space line segments into the buffer of the run that is
4
- // currently executing (an ambient scope the scene loader opens around each synchronous call), and
5
- // the scene editor pushes those buffers to the lite engine's overlay layer, which draws them over
6
- // the picture — never outlined, never picked, never rendered by Filament, absent in play mode.
7
- //
8
- // rebuild() {
9
- // Gizmos.polyline(points, { color: "#5b8ef0", closed: true })
10
- // for (const p of points) Gizmos.cross(p, 0.07)
11
- // }
12
- //
13
- // Every run starts with an EMPTY buffer — what a call draws is the whole picture (like
14
- // `generated.clear()`, only implicit). Outside any scope (play mode, hand-attached aspects, a call
15
- // after an `await` inside rebuild) the calls are no-ops: rebuild is synchronous by contract.
16
- //
17
- // ANCHORED gizmos (`{ node }` in the style): points are in that node's LOCAL frame and the engine
18
- // follows the node live — through a gizmo drag, not only after it — with the node's scale
19
- // stripped (an empty scaled ×10 for its children keeps a normal-sized cross). An anchored picture
20
- // is the node's own: clicking it in the viewport selects the node, and it draws in the selection
21
- // accent while selected. World-space calls (path lines) are never pickable.
22
-
23
- import { Mat4, type Mat4Like } from "../math/mat4"
24
- import type { Vec3Like } from "../math/vec"
25
-
26
- /** The anchor of a gizmo call: any object with an engine entity id (a `Node`). */
27
- export type GizmoAnchor = { readonly id: number }
28
-
29
- export type GizmoStyle = {
30
- /** CSS hex color (`#rgb` / `#rrggbb`); default a neutral light gray. */
31
- color?: string
32
- /** 0..1, default 1. */
33
- alpha?: number
34
- /** Anchor: points are in this node's LOCAL frame (scale ignored); the lines follow the node
35
- * live, are pickable (a click selects the node) and turn the selection accent when it is
36
- * selected. Omit for world-space lines (not pickable). */
37
- node?: GizmoAnchor | null
38
- }
39
-
40
- /** One color batch of LINES (flat `[x,y,z, x,y,z]` per segment) — what a host hands the engine.
41
- * With `entityId` the segments are in that entity's local frame (see GizmoStyle.node). */
42
- export type GizmoBatch = { color: string, alpha: number, segments: number[], entityId?: number }
43
-
44
- const DEFAULT_COLOR = "#cfd4dd"
45
-
46
- /** The per-run collector (owned by the scene loader's EditorRun). Batches are keyed by style so a
47
- * hundred same-colored segments cost one draw. */
48
- export class GizmoBuffer {
49
- batches: GizmoBatch[] = []
50
- private _byStyle = new Map<string, GizmoBatch>()
51
-
52
- clear(): void {
53
- this.batches = []
54
- this._byStyle.clear()
55
- }
56
-
57
- segments(style: GizmoStyle | undefined): number[] {
58
- const color = style?.color ?? DEFAULT_COLOR
59
- const alpha = Math.max(0, Math.min(1, style?.alpha ?? 1))
60
- const entityId = style?.node?.id
61
- const key = `${color}@${alpha}@${entityId ?? ""}`
62
- let b = this._byStyle.get(key)
63
- if (!b) {
64
- b = entityId ? { color, alpha, segments: [], entityId } : { color, alpha, segments: [] }
65
- this._byStyle.set(key, b)
66
- this.batches.push(b)
67
- }
68
- return b.segments
69
- }
70
- }
71
-
72
- let current: GizmoBuffer | null = null
73
- let warnedOutOfScope = false
74
-
75
- const xyz = (p: Vec3Like): [number, number, number] =>
76
- Array.isArray(p) ? [ p[0] ?? 0, p[1] ?? 0, p[2] ?? 0 ] : [ (p as { x: number }).x, (p as { y: number }).y, (p as { z: number }).z ]
77
-
78
- const target = (style: GizmoStyle | undefined): number[] | null => {
79
- if (current) return current.segments(style)
80
- if (!warnedOutOfScope && (globalThis as { __lecodesSceneEdit?: boolean }).__lecodesSceneEdit) {
81
- warnedOutOfScope = true
82
- console.warn("[Gizmos] draw call outside an editor run — gizmos must be drawn synchronously inside rebuild() / a make() factory / a tool hook")
83
- }
84
- return null
85
- }
86
-
87
- /** @internal Run `fn` with `buffer` as the ambient gizmo target (cleared first). Nested scopes
88
- * restore the outer one. */
89
- export const withGizmoScope = <T>(buffer: GizmoBuffer, fn: () => T): T => {
90
- const prev = current
91
- buffer.clear()
92
- current = buffer
93
- try { return fn() } finally { current = prev }
94
- }
95
-
96
- /**
97
- * Editor-only line drawing, available inside generator `rebuild()`, `make()` factories and editor
98
- * tool hooks. Points are WORLD space. Drawn by the scene editor's viewport overlay (depth-test
99
- * off — a path through a wall is still a path); invisible everywhere else.
100
- */
101
- export const Gizmos = {
102
- /** One segment from `a` to `b`. */
103
- line(a: Vec3Like, b: Vec3Like, style?: GizmoStyle): void {
104
- const out = target(style)
105
- if (!out) return
106
- out.push(...xyz(a), ...xyz(b))
107
- },
108
-
109
- /** Consecutive segments through `points`; `closed` joins the last point back to the first. */
110
- polyline(points: readonly Vec3Like[], style?: GizmoStyle & { closed?: boolean }): void {
111
- if (points.length < 2) return
112
- const out = target(style)
113
- if (!out) return
114
- const n = points.length
115
- const last = style?.closed ? n : n - 1
116
- for (let i = 0; i < last; i++) out.push(...xyz(points[i]!), ...xyz(points[(i + 1) % n]!))
117
- },
118
-
119
- /** A wireframe camera frustum looking down −Z: the four edges from the origin to a rect
120
- * `length` metres ahead sized by `fov` (vertical, degrees) × `aspect` (default 16:9), plus an
121
- * "up" fin above the rect. Anchor it (`{ node }`) for a camera node's marker — the frustum
122
- * then follows the node's pose; `matrix` places an unanchored one in world space. */
123
- frustum(fov: number, style?: GizmoStyle & { aspect?: number, length?: number, matrix?: Mat4Like }): void {
124
- const out = target(style)
125
- if (!out) return
126
- const aspect = style?.aspect ?? 16 / 9, length = style?.length ?? 0.6
127
- const m = style?.matrix ? new Mat4(style.matrix) : null
128
- const h = Math.tan((fov * Math.PI) / 360) * length, w = h * aspect, z = -length
129
- const P = (x: number, y: number, zz: number): [number, number, number] => {
130
- if (!m) return [ x, y, zz ]
131
- const v = m.transformPoint([ x, y, zz ])
132
- return [ v.x, v.y, v.z ]
133
- }
134
- const o = P(0, 0, 0)
135
- const c = [ P(-w, -h, z), P(w, -h, z), P(w, h, z), P(-w, h, z) ]
136
- for (const q of c) out.push(...o, ...q)
137
- for (let i = 0; i < 4; i++) out.push(...c[i]!, ...c[(i + 1) % 4]!)
138
- out.push(...P(-w * 0.4, h, z), ...P(0, h * 1.6, z), ...P(0, h * 1.6, z), ...P(w * 0.4, h, z))
139
- },
140
-
141
- /** A three-axis cross centred on `p` (a point marker); `size` = half extent, default 0.1. */
142
- cross(p: Vec3Like, size = 0.1, style?: GizmoStyle): void {
143
- const out = target(style)
144
- if (!out) return
145
- const [ x, y, z ] = xyz(p)
146
- out.push(x - size, y, z, x + size, y, z, x, y - size, z, x, y + size, z, x, y, z - size, x, y, z + size)
147
- },
148
- }
@@ -1,120 +0,0 @@
1
- // The scene-file literal grammar markers, shared by `.scene.ts` (defineScene, 3D) and
2
- // `.scene2d.ts` (defineScene2d, 2D): `use(Ctor, props)` aspect entries, `ref('name')` node
3
- // references, `make(fn, args)` code-built sources — plus the ref-resolution helpers both loaders
4
- // run. One implementation so the two formats can't drift; the engine-specific node/def types stay
5
- // in their own modules (defineScene.ts / defineScene2d.ts), which re-export these for user code.
6
-
7
- import type { Aspect, AspectCtor } from "../core/Aspect"
8
- import type { Node } from "../gl/Node"
9
-
10
- /** One aspect to attach, created by `use(Ctor, props)`. */
11
- export type AspectEntry<A extends Aspect<any, any> = Aspect<any, any>> = {
12
- /** @internal grammar marker. */
13
- readonly __use: true
14
- ctor: AspectCtor<A>
15
- props?: Partial<A>
16
- }
17
-
18
- /** Reference an aspect in a scene file: `aspects: [use(Physics, { motion: 'static' })]`. Props are
19
- * typechecked against the aspect's fields, exactly like `node.aspect(Ctor, props)`. */
20
- export const use = <A extends Aspect<any, any>>(ctor: AspectCtor<A>, props?: Partial<A>): AspectEntry<A> =>
21
- ({ __use: true, ctor, props })
22
-
23
- /** A `make(fn, args)` source entry — see {@link make}. `N` is the node family the factory returns
24
- * (3D `Node` by default; a `.scene2d.ts` file's factories return `Node2D`). */
25
- export type MakeEntry<A extends Record<string, unknown> = Record<string, unknown>, N = Node> = {
26
- /** @internal grammar marker. */
27
- readonly __make: true
28
- fn: (args: A) => N | Promise<N>
29
- args?: A
30
- }
31
-
32
- /**
33
- * A code-created source in a scene file: `tower: { make: make(buildTower, { floors: 5 }), … }`.
34
- * The factory runs after every scene node exists (so `ref()` args resolve, forward references
35
- * included) and its returned subtree mounts under the def node — the def's transform stays
36
- * editor-owned, so moving the node never re-calls the factory. Args must be literal data (same
37
- * grammar as aspect props); in the editor they edit as fields, and an arg change re-CALLS the
38
- * factory live — the code is already in the bundle, so no compile happens. Keep factories pure
39
- * builders: same args → same subtree, no side effects outside the returned nodes.
40
- */
41
- export const make = <A extends Record<string, unknown>, N>(
42
- fn: (args: A) => N | Promise<N>, args?: A,
43
- ): MakeEntry<A, N> => ({ __make: true, fn, args })
44
-
45
- /**
46
- * Reference another scene node by PATH in an aspect's props:
47
- * `use(Road, { from: ref('pointA'), to: ref('lane/pointB') })`. Resolves to the live node when
48
- * aspects attach — after EVERY node of the scene exists, so declaration order doesn't matter.
49
- * Resolution is scoped upward from the host node (like variable scoping): the host's own children
50
- * first, then its siblings, then each ancestor's scope up to the scene root. The FIRST segment
51
- * binds the scope; the remaining segments descend from there. Refs address def nodes only (no
52
- * `::`/`name[i]` asset-internal segments); an unknown path resolves to `null` (type your aspect
53
- * field `Node | null`). Scene files only — hand-written code passes nodes directly:
54
- * `node.aspect(Road, { from: nodes.pointA })`.
55
- */
56
- export const ref = <T = Node>(path: string): T => ({ $ref: path } as unknown as T)
57
-
58
- export const isNodeRef = (v: unknown): v is { $ref: string } =>
59
- typeof v === "object" && v !== null && typeof (v as { $ref?: unknown }).$ref === "string"
60
-
61
- /** Resolve one ref string against a path-keyed nodes record, scoped upward from `hostPath` (own
62
- * children → parent's scope (siblings + the host itself) → each ancestor scope → root, `""`).
63
- * The ref's FIRST segment binds the scope — a match there is final even when the rest of the
64
- * path doesn't exist (lexical shadowing). Returns the target's ABSOLUTE path, or null.
65
- * `Object.hasOwn`, not indexing: a node named `constructor` must not resolve via the prototype. */
66
- export const resolveRefPath = (
67
- nodes: Record<string, unknown>, hostPath: string, ref: string,
68
- ): string | null => {
69
- const i = ref.indexOf("/")
70
- const first = i < 0 ? ref : ref.slice(0, i)
71
- for (let scope = hostPath; ; ) {
72
- const base = scope === "" ? "" : `${scope}/`
73
- if (Object.hasOwn(nodes, base + first)) return Object.hasOwn(nodes, base + ref) ? base + ref : null
74
- if (scope === "") return null
75
- const cut = scope.lastIndexOf("/")
76
- scope = cut < 0 ? "" : scope.slice(0, cut)
77
- }
78
- }
79
-
80
- /** Swap `ref()` markers in aspect props for the live nodes (top level + one array level deep),
81
- * resolved scoped-upward from `hostPath` ("" = root scope only — the 2D flat-map case). */
82
- export const resolveRefs = <N>(
83
- props: Record<string, unknown> | undefined, nodes: Record<string, N>, hostPath = "",
84
- ): Record<string, unknown> | undefined => {
85
- if (!props) return props
86
- const lookup = (r: string): N | null => {
87
- const p = resolveRefPath(nodes, hostPath, r)
88
- return p === null ? null : nodes[p]
89
- }
90
- let out: Record<string, unknown> | undefined
91
- for (const [ k, v ] of Object.entries(props)) {
92
- if (isNodeRef(v)) {
93
- ;(out ??= { ...props })[k] = lookup(v.$ref)
94
- } else if (Array.isArray(v) && v.some(isNodeRef)) {
95
- ;(out ??= { ...props })[k] = v.map((el) => (isNodeRef(el) ? lookup(el.$ref) : el))
96
- }
97
- }
98
- return out ?? props
99
- }
100
-
101
- /** ABSOLUTE paths of the nodes a props record's ref() values resolve to (editor dep tracking).
102
- * An unresolved ref contributes no dep — the handle re-derives deps after structural changes. */
103
- export const collectRefDeps = (
104
- props: Record<string, unknown>, nodes: Record<string, unknown>, hostPath: string,
105
- ): Set<string> => {
106
- const deps = new Set<string>()
107
- const add = (r: string): void => {
108
- const p = resolveRefPath(nodes, hostPath, r)
109
- if (p !== null) deps.add(p)
110
- }
111
- for (const v of Object.values(props)) {
112
- if (isNodeRef(v)) add(v.$ref)
113
- else if (Array.isArray(v)) for (const el of v) if (isNodeRef(el)) add(el.$ref)
114
- }
115
- return deps
116
- }
117
-
118
- /** The edit-mode flag an editor host sets before running the bundle (shared by both formats). */
119
- export const EDIT_FLAG = "__lecodesSceneEdit"
120
- export const isEditMode = (): boolean => (globalThis as Record<string, unknown>)[EDIT_FLAG] === true
@@ -1,188 +0,0 @@
1
- // Materials as data — the `material:` grammar of scene files and the `defineMaterial` document
2
- // behind `*.material.ts` assets (docs/scene-materials-plan.md).
3
- //
4
- // // materials/holo-glass.material.ts
5
- // export default defineMaterial({
6
- // shader: asset('../assets/shaders/lens.mat'),
7
- // params: { tint: '#1d2b26', opacity: 0.22, mask: asset('../assets/weapons/reticle.png') },
8
- // })
9
- //
10
- // // range.scene.ts
11
- // import holoGlass from './materials/holo-glass.material'
12
- // lens: { mesh: { kind: 'plane' }, material: holoGlass },
13
- //
14
- // One grammar serves the inline form (`material: { lit: {…} }` / `{ shader, params }` on a node),
15
- // the asset form (the handle) and GLB-part overrides. A handle is ONE shared GPU material: every
16
- // node using the asset renders the same instance, and editing the asset changes all of them
17
- // (Unity's rule — two looks are two assets).
18
- //
19
- // Parameter values need no shader metadata at runtime: a string is a colour when it starts with
20
- // `#`, otherwise it is a texture URL (`asset('./x.png')` compiles to one) — the same lowering
21
- // `Material.set` already applies to colours.
22
-
23
- import type { ColorInput } from "../core/color"
24
- import { Material, type LitMaterialOptions, type MaterialColorOptions } from "../gl/Material"
25
- import { Texture } from "../gl/Texture"
26
- import type { Node } from "../gl/Node"
27
- import { EDIT_FLAG } from "./grammar"
28
-
29
- /** A shader parameter value as scene files write it: number, boolean, `#colour`, texture URL
30
- * (`asset('./x.png')`), or a numeric vector. */
31
- export type MaterialParamValue = number | boolean | string | number[]
32
-
33
- type WithMapUrl<T> = Omit<T, "map"> & {
34
- /** Base-colour texture — a `Texture`, a `Canvas`, or `asset('./x.png')` (a URL string). */
35
- map?: LitMaterialOptions["map"] | string
36
- }
37
-
38
- export type LitMaterialDef = WithMapUrl<LitMaterialOptions>
39
- export type UnlitMaterialDef = WithMapUrl<MaterialColorOptions>
40
-
41
- /** A custom Filament shader (`asset('./x.mat')` — compiled by the platform / `lecodes shaders`)
42
- * plus its parameter values. */
43
- export type ShaderMaterialDef = {
44
- shader: string
45
- params?: Record<string, MaterialParamValue>
46
- }
47
-
48
- export type MaterialDef =
49
- | { lit: LitMaterialDef }
50
- | { unlit: UnlitMaterialDef }
51
- | { shadow: ColorInput }
52
- | ShaderMaterialDef
53
- /** A material asset (`import m from './x.material'`) — one shared instance. */
54
- | MaterialHandle
55
- /** A code-level Material instance — valid at runtime; the editor shows it read-only. */
56
- | Material
57
-
58
- /** The data forms of MaterialDef (everything but a live instance / handle). */
59
- export type MaterialData = Exclude<MaterialDef, MaterialHandle | Material>
60
-
61
- const isTextureUrl = (v: unknown): v is string => typeof v === "string" && !v.startsWith("#")
62
-
63
- /** Push one parameter into a live material. Textures load asynchronously — the returned promise
64
- * settles once the value is applied (immediately for scalars). */
65
- export const applyMaterialParam = (material: Material, name: string, value: MaterialParamValue | null): Promise<void> => {
66
- if (isTextureUrl(value)) {
67
- return Texture.load(value).then((tex) => { material.set(name, tex) }, (e) => {
68
- console.warn(`[material] "${name}": ${e instanceof Error ? e.message : String(e)}`)
69
- })
70
- }
71
- material.set(name, value as never)
72
- return Promise.resolve()
73
- }
74
-
75
- /** Build a live Material from a data def. Resolves once every texture it names has loaded. */
76
- export const createMaterialFromData = async (def: MaterialData): Promise<Material> => {
77
- if ("shader" in def) {
78
- const m = await Material.load(def.shader)
79
- await Promise.all(Object.entries(def.params ?? {}).map(([ k, v ]) => applyMaterialParam(m, k, v)))
80
- return m
81
- }
82
- if ("shadow" in def) return Material.shadow(def.shadow)
83
- const lit = "lit" in def
84
- const opts = lit ? def.lit : def.unlit
85
- const { map, ...rest } = opts
86
- const m = lit ? Material.lit(rest as LitMaterialOptions) : Material.unlit(rest as MaterialColorOptions)
87
- if (isTextureUrl(map)) m.map = await Texture.load(map)
88
- else if (map !== undefined) m.map = map as LitMaterialOptions["map"] as Texture | null
89
- return m
90
- }
91
-
92
- /**
93
- * A material asset: the default export of a `*.material.ts` file. `load()` yields ONE shared
94
- * `Material` (cached); scene files hand the handle to `material:` and the loader resolves it.
95
- * The editor edits the asset live through `setParam` / `reset` — every node the handle was
96
- * applied to follows (`_users`).
97
- */
98
- export class MaterialHandle {
99
- def: MaterialData
100
- private _instance: Material | null = null
101
- private _loading: Promise<Material> | null = null
102
- /** Every (node, slot) this handle was applied to through a scene — the editor re-assigns them
103
- * when the asset's shader changes. */
104
- readonly _users = new Set<{ node: Node, slot: number }>()
105
-
106
- constructor(def: MaterialData) {
107
- this.def = def
108
- }
109
-
110
- /** The shared material instance (built on first use). */
111
- load(): Promise<Material> {
112
- if (this._instance) return Promise.resolve(this._instance)
113
- this._loading ??= createMaterialFromData(this.def).then((m) => { this._instance = m; return m })
114
- return this._loading
115
- }
116
-
117
- /** The live instance, if built yet. */
118
- get material(): Material | null { return this._instance }
119
-
120
- /** Editor: assign this material to `node`'s `slot`, remembering the user. */
121
- async _applyTo(node: Node, slot: number): Promise<Material> {
122
- const m = await this.load()
123
- node.setMaterial(m, slot)
124
- this._users.add({ node, slot })
125
- return m
126
- }
127
-
128
- /** Editor: one parameter value, applied live to the shared instance (a texture loads in). */
129
- setParam(name: string, value: MaterialParamValue | null): void {
130
- const def = this.def
131
- if ("shader" in def) {
132
- def.params ??= {}
133
- if (value === null) delete def.params[name]
134
- else def.params[name] = value
135
- } else if ("lit" in def || "unlit" in def) {
136
- const opts = ("lit" in def ? def.lit : def.unlit) as Record<string, unknown>
137
- if (value === null) delete opts[name]
138
- else opts[name] = value
139
- }
140
- const m = this._instance
141
- if (!m) return
142
- if (!("shader" in def) && name === "color") { m.color = value as string; return }
143
- if (!("shader" in def) && name === "map") {
144
- if (isTextureUrl(value)) void Texture.load(value).then((t) => { m.map = t })
145
- else m.map = null
146
- return
147
- }
148
- void applyMaterialParam(m, name, value)
149
- }
150
-
151
- /** Editor: replace the whole definition (a shader/kind switch) — rebuilds the instance and
152
- * re-assigns every user. */
153
- async reset(def: MaterialData): Promise<Material> {
154
- this.def = def
155
- this._instance = null
156
- this._loading = null
157
- const m = await this.load()
158
- for (const u of this._users) u.node.setMaterial(m, u.slot)
159
- return m
160
- }
161
- }
162
-
163
- /** Resolve any `material:` value to a live Material (a handle's shared instance, a data def's
164
- * fresh one, a code instance as is). */
165
- export const resolveMaterialDef = (def: MaterialDef): Promise<Material> => {
166
- if (def instanceof Material) return Promise.resolve(def)
167
- if (def instanceof MaterialHandle) return def.load()
168
- return createMaterialFromData(def)
169
- }
170
-
171
- /** Resolve and assign a `material:` value to one slot of a node (tracking handle users). */
172
- export const assignMaterialDef = async (node: Node, slot: number, def: MaterialDef): Promise<Material> => {
173
- if (def instanceof MaterialHandle) return def._applyTo(node, slot)
174
- const m = await resolveMaterialDef(def)
175
- node.setMaterial(m, slot)
176
- return m
177
- }
178
-
179
- /**
180
- * Define a material as data — the default export of a `*.material.ts` file. Import it into scene
181
- * files (`material: handle`) or code (`const m = await handle.load()`).
182
- */
183
- export const defineMaterial = (def: MaterialData): MaterialHandle => {
184
- const handle = new MaterialHandle(def)
185
- const g = globalThis as unknown as { [EDIT_FLAG]?: boolean, __lecodesMaterialHandles?: MaterialHandle[] }
186
- if (g[EDIT_FLAG]) (g.__lecodesMaterialHandles ??= []).push(handle)
187
- return handle
188
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * The project's `app.json` subset the server runtime reads (name, auth providers).
3
- *
4
- * This module is a placeholder the COMPILER REPLACES: `compileServerBundle({ app })` puts a module with the
5
- * real values at this same path in chisel's file map (project files win over SDK files), so every SDK module
6
- * that imports `app` gets the project's config as a plain dependency — no globals, no evaluation-order games.
7
- * Outside a compiled bundle (tests, tooling) `globalThis.__lecodesApp` can stand in.
8
- */
9
- export type AuthProvider = "email" | "google" | "apple" | "yandex" | "vk"
10
- export type AppConfig = { name?: string, auth?: { providers?: AuthProvider[] } }
11
-
12
- export const app: AppConfig = {}
@@ -1,80 +0,0 @@
1
- /**
2
- * The `auth` server global (docs/backend-plan.md §3.4) — the BUNDLE side. State (`auth.user`,
3
- * `auth.session`) is read from the current request, which the host resolved before the endpoint ran;
4
- * operations (`auth.email.*`, `auth.oauth.url`, `signIn/signOut`, `sessions`) forward to the host's
5
- * `AuthOps` on `globalThis.__lecodesAuth` (a seam: the compiled bundle carries its own SDK copy). Nothing
6
- * here touches the db or a secret; `auth.model.*` are pure schema factories.
7
- */
8
-
9
- import { currentRequest, type RequestContext } from "../context"
10
- import type { Model, Query } from "../db/types"
11
- import { ApiError } from "../errors"
12
- import { authSessionModel, authUserModel, type AuthProvider, type SessionFields, type UserFields } from "./models"
13
- import type { AuthOps, AuthState, AuthUser, OAuthCredential } from "./types"
14
-
15
- const seam = globalThis as unknown as { __lecodesAuth?: AuthOps }
16
- const ops = (): AuthOps => {
17
- const o = seam.__lecodesAuth
18
- if (!o) throw new Error("auth: not available — the host did not install the auth runtime (globalThis.__lecodesAuth)")
19
- return o
20
- }
21
- const ctx = (): RequestContext => {
22
- const c = currentRequest()
23
- if (!c) throw new Error("auth: no current request — auth.* is only available while handling an endpoint or a channel join")
24
- return c
25
- }
26
- const state = (): AuthState => {
27
- const c = ctx()
28
- const s = c.auth as AuthState | undefined
29
- if (!s) throw new Error("auth: this project has no database — sessions need a defineDb() (auth.model.* are optional, Session is added for you)")
30
- return s
31
- }
32
-
33
- /** The schema of the two built-in models, for typing `auth.sessions` (developer extras aren't visible here). */
34
- type AuthSchema = { User: Model<UserFields>, Session: Model<SessionFields> }
35
-
36
- export const auth = {
37
- /** The signed-in user (built-in fields; custom ones via `db.user`) or `null`. Request-scoped. */
38
- get user(): AuthUser | null { return state().user },
39
- /** The device session — exists from the first request, before any login. */
40
- get session(): { readonly id: number } { return { id: state().sessionId } },
41
- /**
42
- * The signed-in user, or `throw new ApiError(401)`; with `match`, every given field must equal the user's
43
- * (`{ role: "admin" }`, `{ plan: "pro" }`) or it's a 403.
44
- */
45
- requireUser(match?: Record<string, unknown>): AuthUser {
46
- const u = state().user
47
- if (!u) throw new ApiError(401, "Sign in required")
48
- if (match) for (const [k, v] of Object.entries(match)) if ((u as Record<string, unknown>)[k] !== v) throw new ApiError(403, "Forbidden")
49
- return u
50
- },
51
- email: {
52
- /** Emails a 6-digit code to `email` (always resolves — no "email exists" oracle; rate-limited). Needs `"email"` in app.json providers. */
53
- sendCode: (email: string): Promise<void> => ops().email.sendCode(ctx(), email),
54
- /** The only email sign-in: checks the code sent to this device session, finds/creates the `User`, binds the session. */
55
- verify: (email: string, code: string): Promise<AuthUser> => ops().email.verify(ctx(), email, code),
56
- },
57
- oauth: {
58
- /** A one-time sign-in URL for the platform's OAuth broker (`"google" | "apple" | "yandex" | "vk"`), bound to this device session — open it in the browser. */
59
- url: (provider: Exclude<AuthProvider, "email">): Promise<string> => ops().oauth.url(ctx(), provider),
60
- /**
61
- * Sign in with a credential a NATIVE provider SDK produced in the app (client `OAuth.signIn(provider)`):
62
- * the platform verifies it with the provider, finds/links/creates the `User`, binds this session. The
63
- * in-app counterpart of the browser broker — same accounts either way.
64
- */
65
- signIn: (provider: Exclude<AuthProvider, "email">, credential: OAuthCredential): Promise<AuthUser> => ops().oauth.signIn(ctx(), provider, credential),
66
- },
67
- /** Escape hatch for developer-verified identities (Telegram initData, SSO): binds this session to `userId`. */
68
- signIn: (userId: number): Promise<AuthUser> => ops().signIn(ctx(), userId),
69
- /** Unbinds the user; the device stays a guest with the same `session.id` (token rotated). */
70
- signOut: (): Promise<void> => ops().signOut(ctx()),
71
- /** The current user's sessions ("my devices"): a `db.session` query — `await`, `.select()`, `.where(...).updateMany({ revoked: true })`. */
72
- get sessions(): Query<AuthSchema, "Session"> { return ops().sessions(ctx()) },
73
- /** Schema-time factories — see ./models.ts. */
74
- model: {
75
- user: authUserModel,
76
- session: authSessionModel,
77
- },
78
- }
79
-
80
- export type { AuthUser }