lecodes-sdk 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -76
- package/dist/global.d.ts +3 -5
- package/dist/host.d.ts +3 -0
- package/dist/types/inject.d.ts +4 -4
- package/dist/types/net/codec.d.ts +3 -2
- package/dist/types/net/core.d.ts +10 -1
- package/dist/types/net/index.d.ts +22 -5
- package/dist/types/net/replication.d.ts +23 -2
- package/dist/types/runtime/device.d.ts +7 -0
- package/dist/types/runtime/rpc.d.ts +11 -17
- package/dist/types/runtime/wire.d.ts +53 -0
- package/dist/types/server/auth/api.d.ts +42 -0
- package/dist/types/server/auth/appConfig.d.ts +1 -5
- package/dist/types/server/auth/models.d.ts +119 -70
- package/dist/types/server/auth/types.d.ts +19 -43
- package/dist/types/server/channel.d.ts +57 -19
- package/dist/types/server/context.d.ts +2 -2
- package/dist/types/server/db/defineDb.d.ts +10 -0
- package/dist/types/server/db/index.d.ts +1 -1
- package/dist/types/server/db/types.d.ts +76 -6
- package/dist/types/server/inject.d.ts +0 -1
- package/dist/types/ui/UINode.d.ts +19 -5
- package/dist/types/ui/UIScreen.d.ts +1 -0
- package/dist/types/ui/UITabs.d.ts +8 -6
- package/dist/types/ui/theme.d.ts +48 -13
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +4 -2
- package/prompts/README.md +1 -1
- package/prompts/design.md +19 -19
- package/prompts/dist/2d-game.md +45 -31
- package/prompts/dist/3d-app.md +45 -31
- package/prompts/dist/ar-app.md +45 -31
- package/prompts/dist/design.md +25 -24
- package/prompts/dist/ui-app.md +45 -31
- package/prompts/ui-design.md +6 -5
- package/prompts/ui.md +25 -22
- package/src/animate/tween/read.ts +146 -0
- package/src/bridges/device.d.ts +9 -0
- package/src/bridges/tree.d.ts +5 -0
- package/src/canvas/gen/cssColor.ts +1 -1
- package/src/canvas/gen/recorder.ts +1 -1
- package/src/canvas/gen/spec.ts +1 -1
- package/src/chisel.ts +1 -1
- package/src/compile/bundler.ts +6 -0
- package/src/compile/compileProject.ts +3 -1
- package/src/compile/index.ts +3 -1
- package/src/compile/serverSplit.ts +58 -11
- package/src/compile/serverTypes.ts +189 -8
- package/src/host.d.ts +3 -0
- package/src/inject.ts +7 -7
- package/src/net/codec.ts +19 -10
- package/src/net/core.ts +18 -5
- package/src/net/index.ts +30 -9
- package/src/net/replication.ts +63 -28
- package/src/runtime/device.ts +12 -0
- package/src/runtime/rpc.ts +101 -40
- package/src/runtime/wire.ts +35 -0
- package/src/server/auth/api.ts +94 -0
- package/src/server/auth/appConfig.ts +2 -3
- package/src/server/auth/host.ts +244 -174
- package/src/server/auth/models.ts +45 -62
- package/src/server/auth/types.ts +19 -34
- package/src/server/channel.ts +97 -29
- package/src/server/channelHub.ts +153 -0
- package/src/server/context.ts +2 -2
- package/src/server/db/defineDb.ts +96 -36
- package/src/server/db/index.ts +1 -1
- package/src/server/db/types.ts +76 -8
- package/src/server/host.ts +25 -10
- package/src/server/inject.ts +2 -2
- package/src/server/runtime.ts +34 -12
- package/src/ui/UINode.ts +22 -5
- package/src/ui/UIScreen.ts +5 -0
- package/src/ui/UITabs.ts +19 -17
- package/src/ui/styleColor.ts +10 -1
- package/src/ui/theme.ts +96 -41
- package/src/version.ts +1 -1
- package/tests/helpers/fakeTree.ts +1 -0
- package/dist/types/plugins/oauth.d.ts +0 -25
- package/dist/types/server/auth/global.d.ts +0 -56
- package/src/plugins/oauth.ts +0 -61
- package/src/server/auth/global.ts +0 -80
- package/tests/helpers/memoryMarci.ts +0 -124
package/README.md
CHANGED
|
@@ -1,52 +1,76 @@
|
|
|
1
|
-
# sdk — the
|
|
1
|
+
# lecodes-sdk — the import-free LeCodes API
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
(
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
The TypeScript API LeCodes apps are written against: **UI**, **2D**, **3D** and the platform
|
|
4
|
+
runtime (network, storage, input, audio, device, plugins) in one surface of **globals** — user code
|
|
5
|
+
writes `UIScreen`, `Scene`, `Sprite`, `fetch`, … with **no imports**. The `lecodes` CLI compiles a
|
|
6
|
+
project through this package into one `app.js`; the bundler (chisel) keeps only the methods a
|
|
7
|
+
project reaches, so a UI app carries no 3D code and a 2D game no UI kit. The same bundle runs on
|
|
8
|
+
the LeCodes hosts: desktop, iOS, Android and the web player.
|
|
7
9
|
|
|
8
|
-
|
|
10
|
+
**API reference:** [docs/index.md](./docs/index.md) — every global, example-first; how to build
|
|
11
|
+
a whole thing out of it is the [cookbook](./docs/cookbook/README.md). `bun run docs:check` verifies
|
|
12
|
+
that every global has exactly one page and that the links resolve.
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
```ts
|
|
15
|
+
// UI — no imports
|
|
16
|
+
let taps = 0
|
|
17
|
+
const label = UIText('Taps: 0').style({ color: '#fff', fontSize: 22 })
|
|
18
|
+
|
|
19
|
+
UIScreen(
|
|
20
|
+
label,
|
|
21
|
+
UIButton(UIText('Tap me').style({ color: '#fff' }))
|
|
22
|
+
.style({ bgColor: '#4f7cff', borderRadius: 12, p: 16, $pressed: { opacity: 0.8 } })
|
|
23
|
+
.onClick(() => { label.text = `Taps: ${++taps}` }),
|
|
24
|
+
)
|
|
25
|
+
.style({ bgColor: '#1e2230', alignItems: 'center', justifyContent: 'center', gap: 16 })
|
|
26
|
+
.open()
|
|
27
|
+
```
|
|
12
28
|
|
|
13
29
|
```ts
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// 3D — no imports; behaviour is an Aspect attached to a node
|
|
31
|
+
Physics.configure({ gravity: [0, -9.81, 0] })
|
|
32
|
+
|
|
33
|
+
class Turn extends Aspect<'turn'> {
|
|
34
|
+
speed = 90 // degrees per second
|
|
35
|
+
private angle = 0
|
|
36
|
+
protected update(dt: number) { // every frame; dt = game seconds
|
|
37
|
+
this.angle += this.speed * dt
|
|
38
|
+
this.node.eulerAngles = [0, this.angle, 0]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const scene = new Scene({ skybox: '#10131a' })
|
|
43
|
+
const mat = Material.lit({ color: '#e33' })
|
|
44
|
+
const ground = Mesh.box({ material: mat, scale: [10, 0.2, 10] })
|
|
45
|
+
.aspect(Shape, {}) // a box collider fitted to the mesh
|
|
46
|
+
.aspect(Physics, { motion: 'static' })
|
|
47
|
+
const ball = Mesh.sphere({ material: mat, position: [0, 5, 0] })
|
|
48
|
+
.aspect(Shape, {})
|
|
49
|
+
.aspect(Physics, { mass: 1 }) // dynamic: the physics step moves it
|
|
50
|
+
const cube = Mesh.box({ material: mat, position: [2, 1, 0] })
|
|
51
|
+
.aspect(Turn, { speed: 180 }) // cube.turn is the attached aspect
|
|
52
|
+
scene.add(ground, ball, cube, Light.sun())
|
|
53
|
+
scene.camera.position = [0, 6, 12]
|
|
54
|
+
scene.camera.lookAt([0, 0, 0])
|
|
55
|
+
scene.open()
|
|
31
56
|
```
|
|
32
57
|
|
|
33
58
|
## Fluent math (`Vec2` / `Vec3` / `Quat` / `Mat4`)
|
|
34
59
|
|
|
35
|
-
Per-frame math reads like the formula — no
|
|
36
|
-
|
|
37
|
-
never mutates its source. They
|
|
38
|
-
a vector does.
|
|
60
|
+
Per-frame math reads like the formula — no out-params. Vectors are *mutable structs with pure
|
|
61
|
+
methods*: fields are settable (`v.x = 3`, `v.set(…)`), but every method returns a NEW value and
|
|
62
|
+
never mutates its source. They are iterable and accept raw tuples anywhere, so `[0, 1, 0]` works
|
|
63
|
+
wherever a vector does.
|
|
39
64
|
|
|
40
65
|
```ts
|
|
41
|
-
const dir
|
|
66
|
+
const dir = target.position.sub(self.position).normalize()
|
|
42
67
|
self.position = self.position.add(dir.scale(speed * dt))
|
|
43
68
|
if (a.distanceTo(b) < 2) explode()
|
|
44
69
|
node.quaternion = node.quaternion.mul(Quat.fromAxisAngle(Vec3.up, turn))
|
|
45
70
|
|
|
46
|
-
//
|
|
47
|
-
node.x = 3
|
|
48
|
-
const p = node.position; p.y = 5; node.position = p
|
|
49
|
-
// node.position.x = 3 ← a no-op (mutates a discarded copy); use node.x instead
|
|
71
|
+
// node getters return fresh copies (value semantics, like Unity):
|
|
72
|
+
node.position.x = 3 // compiled to a single-axis write — the direct spelling works
|
|
73
|
+
const p = node.position; p.y = 5; node.position = p // a stored copy: mutate it, assign it back
|
|
50
74
|
|
|
51
75
|
// Mat4 keeps the metal exposed (public .m) for low-level surgery:
|
|
52
76
|
let m = node.matrix.translate(pivot).rotateY(yaw * dt).translate(pivot.negate())
|
|
@@ -54,64 +78,68 @@ m.m[12] += vx * dt
|
|
|
54
78
|
node.matrix = m
|
|
55
79
|
```
|
|
56
80
|
|
|
57
|
-
`Quat
|
|
58
|
-
perspective`. All of it is fuzz-tested against gl-matrix (dev-only oracle) in
|
|
81
|
+
`Quat` / `Mat4` cover the rest: `Quat.fromEuler / lookRotation / slerp`, `Mat4.compose / decompose /
|
|
82
|
+
lookAt / perspective`. All of it is fuzz-tested against gl-matrix (a dev-only oracle) in
|
|
83
|
+
`tests/math/oracle.test.ts`.
|
|
59
84
|
|
|
60
85
|
## Design rules
|
|
61
86
|
|
|
62
|
-
- **
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
87
|
+
- **Globals only.** `src/inject.ts` is the one file that exports public names; a project's imports
|
|
88
|
+
are relative paths to its own files and assets. Variants are static factories (`Mesh.box()`,
|
|
89
|
+
`Material.lit()`, `Texture2D.load()`), not free functions.
|
|
90
|
+
- **UI elements are handles** to the runtime's tree (docs/tree.md at the repo root): factories take
|
|
91
|
+
content, appearance goes through chained `.style()`, states are `$name` class blocks.
|
|
92
|
+
- **Behaviour is an `Aspect`** on a node and a **`System`** on a scene; the phases are methods
|
|
93
|
+
(`updateBefore` / `update` / `updateFixed`) — no hand-written game loops.
|
|
94
|
+
- **Native resources are owned handles**: a node, texture, material or player is freed once nothing
|
|
95
|
+
references it and nothing native owns it; `destroy()` frees a node early.
|
|
96
|
+
- **Hosts differ, the API does not**: an optional host capability is feature-detected
|
|
97
|
+
(`Physics.supported`, a plugin's `isSupported`), never versioned.
|
|
68
98
|
|
|
69
99
|
## Layout
|
|
70
100
|
|
|
71
101
|
```
|
|
72
102
|
src/
|
|
73
|
-
inject.ts
|
|
74
|
-
|
|
75
|
-
host.d.ts
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
103
|
+
inject.ts THE aggregation point — the only file that exports public globals
|
|
104
|
+
host.d.ts host-provided globals (setTimeout, setLoop, console, asset, font, DEG2RAD, EDITOR)
|
|
105
|
+
bridges/ the host contract: one .d.ts per bridge (_creator*, host tables) — never bundled
|
|
106
|
+
chisel.ts the chisel vocabulary: every SDK name the bundler's passes key on
|
|
107
|
+
version.ts SDK_VERSION — its major is the bundle ↔ runtime contract
|
|
108
|
+
core/ Aspect / System, signals, events, Time, StateMachine, color, owned-handle pins
|
|
109
|
+
math/ Mathf, Vec2 / Vec3, Quat, Mat4 (no gl-matrix shipped)
|
|
110
|
+
canvas/ Canvas / Bitmap — vector drawing over the AnyCanvas core
|
|
111
|
+
animate/ animate(), Timeline, easings — the keyframe tween core
|
|
112
|
+
runtime/ fetch, storage, files, device, input, media, touch, app, share, date, rpc
|
|
113
|
+
audio/ game audio: Sound, Voice, Bus, audio
|
|
114
|
+
net/ multiplayer: Net, replication
|
|
115
|
+
ui/ UIScreen / UIRow / UIText / … + Router, UIPager, widgets, theme, transitions
|
|
116
|
+
g2/ 2D: Scene2D, Node2D, Sprite, Tilemap, tilesets, Box2D physics
|
|
117
|
+
gl/ 3D: Scene, Node, Mesh, Model, Material, Light, physics/, vehicle/, animation/, nav/, terrain/
|
|
118
|
+
scene/ scenes as data (defineScene), materials, Level, editor plugins
|
|
119
|
+
plugins/ OAuth, Service + the first-party plugins vendored under plugins/gen
|
|
120
|
+
server/ the host side of app backends (`lecodes-sdk/server`)
|
|
121
|
+
compile/ the project compiler (bundler, entry detection, macros) — `lecodes-sdk/compile`
|
|
122
|
+
docs/ the API reference + cookbook
|
|
123
|
+
prompts/ codegen prompts for the platform's in-editor assistant (`bun run prompts:build`)
|
|
124
|
+
tests/<area>/ bun tests, one folder per src area; tests/helpers = the fake host (`lecodes-sdk/testing`)
|
|
86
125
|
```
|
|
87
126
|
|
|
88
127
|
## Build & test
|
|
89
128
|
|
|
90
129
|
```sh
|
|
91
|
-
bun run build #
|
|
92
|
-
bun test #
|
|
130
|
+
bun run build # dist/global.d.ts + host.d.ts + editor.d.ts + types.json — the editor types
|
|
131
|
+
bun test # every suite; `bun test tests/<area>` for one area
|
|
93
132
|
bun run typecheck # tsc --noEmit
|
|
133
|
+
bun run docs:check # every global documented exactly once, sources + links resolve
|
|
94
134
|
```
|
|
95
135
|
|
|
96
|
-
|
|
97
|
-
has the layout, the shared stubs and the one-process rules.
|
|
98
|
-
|
|
99
|
-
`tests/headless/` holds live WebGL2 render harnesses (manual; need chrome-headless-shell). The 2D
|
|
100
|
-
harness is green: a project compiled through the real `compileProject` renders correct pixels.
|
|
136
|
+
[tests/README.md](tests/README.md) has the test layout, the shared stubs and the one-process rules.
|
|
101
137
|
|
|
102
138
|
## How it integrates
|
|
103
139
|
|
|
104
|
-
The
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
## Deferred (API present, native pending)
|
|
111
|
-
|
|
112
|
-
- 2D physics / collider triggers (`.collider()` is a no-op stub; `Node2D.add()` hierarchy).
|
|
113
|
-
- Live 2D↔3D engine switching is wired in the viewer host (two canvases) but pending a real dual
|
|
114
|
-
project to verify.
|
|
115
|
-
|
|
116
|
-
`ARScene.addControls(target, options?)` ports the AR object-placement gesture recipe (one-finger drag,
|
|
117
|
-
two-finger pinch-scale + twist-rotate) onto the fluent math; returns a `{ remove() }` handle.
|
|
140
|
+
The CLI (`lecodes compile | render | test | dev`) and the build service compile every project
|
|
141
|
+
through `lecodes-sdk/compile`: entry detection (`main.ts`, else the import-graph root), the
|
|
142
|
+
`asset()` / `font()` / `assetIcon()` macros, then chisel over the SDK **source** — the bundle is
|
|
143
|
+
built from `src/`, not from a prebuilt runtime. Every bundle opens with `// sdk: <version>`; a host
|
|
144
|
+
refuses a bundle whose major differs from its runtime's (the SDK and the runtimes are one version,
|
|
145
|
+
`bun run set-version` at the repo root). Plugins import the SDK through `lecodes-sdk/plugin`.
|
package/dist/global.d.ts
CHANGED
|
@@ -71,7 +71,6 @@ declare global {
|
|
|
71
71
|
const Node: typeof SDK.Node
|
|
72
72
|
const Node2D: typeof SDK.Node2D
|
|
73
73
|
const Noise: typeof SDK.Noise
|
|
74
|
-
const OAuth: typeof SDK.OAuth
|
|
75
74
|
const OneWay2D: typeof SDK.OneWay2D
|
|
76
75
|
const Particles: typeof SDK.Particles
|
|
77
76
|
const Physics: typeof SDK.Physics
|
|
@@ -246,10 +245,10 @@ declare global {
|
|
|
246
245
|
type CameraView = SDK.CameraView
|
|
247
246
|
type Canvas = SDK.Canvas
|
|
248
247
|
type CellsData = SDK.CellsData
|
|
248
|
+
type ChannelSubscription = SDK.ChannelSubscription
|
|
249
249
|
type CharacterController = SDK.CharacterController
|
|
250
250
|
type CharacterController2D = SDK.CharacterController2D
|
|
251
251
|
type ClickEvent<T = any> = SDK.ClickEvent<T>
|
|
252
|
-
type ClientChannel<E extends Record<string, unknown> = Record<string, unknown>> = SDK.ClientChannel<E>
|
|
253
252
|
type ClipAlign = SDK.ClipAlign
|
|
254
253
|
type ClipCycle = SDK.ClipCycle
|
|
255
254
|
type ClipDef = SDK.ClipDef
|
|
@@ -390,6 +389,7 @@ declare global {
|
|
|
390
389
|
type NavPath = SDK.NavPath
|
|
391
390
|
type NavQueryOptions = SDK.NavQueryOptions
|
|
392
391
|
type NavRaycastHit = SDK.NavRaycastHit
|
|
392
|
+
type NetCorrection = SDK.NetCorrection
|
|
393
393
|
type NetEntity<S extends object = any> = SDK.NetEntity<S>
|
|
394
394
|
type NetEvents = SDK.NetEvents
|
|
395
395
|
type NetInput<T extends object = any> = SDK.NetInput<T>
|
|
@@ -403,8 +403,6 @@ declare global {
|
|
|
403
403
|
type Node = SDK.Node
|
|
404
404
|
type Node2D = SDK.Node2D
|
|
405
405
|
type Noise = SDK.Noise
|
|
406
|
-
type OAuthCredential = SDK.OAuthCredential
|
|
407
|
-
type OAuthProviderName = SDK.OAuthProviderName
|
|
408
406
|
type OneWay2D = SDK.OneWay2D
|
|
409
407
|
type OrientationLock = SDK.OrientationLock
|
|
410
408
|
type Particles = SDK.Particles
|
|
@@ -496,6 +494,7 @@ declare global {
|
|
|
496
494
|
type Texture2D = SDK.Texture2D
|
|
497
495
|
type TextureLoadOptions = SDK.TextureLoadOptions
|
|
498
496
|
type ThemeAccessors<T> = SDK.ThemeAccessors<T>
|
|
497
|
+
type ThemeRoles = SDK.ThemeRoles
|
|
499
498
|
type ThemeValues = SDK.ThemeValues
|
|
500
499
|
type TileBlock = SDK.TileBlock
|
|
501
500
|
type TileMaterial = SDK.TileMaterial
|
|
@@ -574,7 +573,6 @@ declare global {
|
|
|
574
573
|
type With<N, A extends Aspect<any, any, any>> = SDK.With<N, A>
|
|
575
574
|
// ---- server globals (available in *.server.ts; using them in client code is a build error) ----
|
|
576
575
|
const ApiError: typeof SERVER.ApiError
|
|
577
|
-
const auth: typeof SERVER.auth
|
|
578
576
|
const channel: typeof SERVER.channel
|
|
579
577
|
const defineDb: typeof SERVER.defineDb
|
|
580
578
|
const model: typeof SERVER.model
|
package/dist/host.d.ts
CHANGED
|
@@ -31,6 +31,9 @@ declare global {
|
|
|
31
31
|
/** Compile-time macro: `asset('./data.json')` yields the file's PARSED data (a .json file is
|
|
32
32
|
* data, not code — it ships inside the bundle). See the `string` overload for everything else. */
|
|
33
33
|
function asset(path: `${string}.json`): any
|
|
34
|
+
/** Compile-time macro: `asset('./logo.svg')` yields the file as an SVG image source — for
|
|
35
|
+
* `UIImage(...)` and `bgImage`. */
|
|
36
|
+
function asset(path: `${string}.svg`): { readonly svg: string, tintColor: string | null }
|
|
34
37
|
/** Compile-time macro: `asset('./hero.png')` is desugared by the bundler into the module import
|
|
35
38
|
* for that resource. The file must exist — a path that resolves to nothing fails the compile
|
|
36
39
|
* (`asset not found: ./hero.png (main.ts:3)`); there is no runtime fallback. Calls inside
|
package/dist/types/inject.d.ts
CHANGED
|
@@ -20,8 +20,9 @@ export { localStorage } from "./runtime/storage";
|
|
|
20
20
|
export { device, type HapticStyle, type MotionOptions } from "./runtime/device";
|
|
21
21
|
export { Input, InputChannel, type InputKeyEvent, type InputGamepadEvent, type InputEventName, type GamepadState, type GamepadAxisName } from "./runtime/input";
|
|
22
22
|
export { WebSocket } from "./runtime/net";
|
|
23
|
-
export { Net, NetPlayer, Replicated, NetEntity, type NetMessage, type NetRole, type NetStatus, type NetEvents, type NetLaunch, type NetInput, type NetKind, type NetTransform } from "./net";
|
|
24
|
-
export { __rpc, __channel, __serverOnly, RpcError
|
|
23
|
+
export { Net, NetPlayer, Replicated, NetEntity, type NetMessage, type NetRole, type NetStatus, type NetEvents, type NetLaunch, type NetInput, type NetKind, type NetTransform, type NetCorrection } from "./net";
|
|
24
|
+
export { __rpc, __channel, __serverOnly, RpcError } from "./runtime/rpc";
|
|
25
|
+
export type { ChannelSubscription } from "./server/channel";
|
|
25
26
|
export { AudioPlayer, VideoPlayer } from "./runtime/media";
|
|
26
27
|
export { Sound, type SoundOptions } from "./audio/Sound";
|
|
27
28
|
export { Voice, type VoiceEvents } from "./audio/Voice";
|
|
@@ -57,7 +58,7 @@ export { UIScreen, type UIScreenStyle, UIRow, UIColumn, UIBox, type UIContainerS
|
|
|
57
58
|
export { Presentable } from "./ui/UI";
|
|
58
59
|
export type { DismissOptions, PresentOptions, Transition, TransitionName, TransitionPose, TransitionSpec } from "./ui/UI";
|
|
59
60
|
export type { UINode, UINodeChild } from "./ui/UINode";
|
|
60
|
-
export { theme, type ThemeValues, type ThemeAccessors } from "./ui/theme";
|
|
61
|
+
export { theme, type ThemeValues, type ThemeAccessors, type ThemeRoles } from "./ui/theme";
|
|
61
62
|
export { animate, type AnimateOptions, type AnimateValue, type AnimateOut } from "./animate/tween/animateValue";
|
|
62
63
|
export { cubicBezier } from "./animate/bezier";
|
|
63
64
|
export { Timeline, type TimelineOptions, type TimelineAddOptions, type TimelinePosition } from "./animate/tween/Timeline";
|
|
@@ -70,7 +71,6 @@ export { CameraView, type CameraFacing } from "./plugins/gen/camera/sdk/camera";
|
|
|
70
71
|
export { Geolocation, type GeoPosition, type GeoOptions, type GeoWatch } from "./plugins/gen/geolocation/sdk/geolocation";
|
|
71
72
|
export { MapView, type MapOptions, type MapStyle, type MapStyleName, type MapCamera, type MapTap, type LngLat, type CameraMove, type FitOptions, type MapPaddingValues, type MarkerItem, type MarkerTap, type MarkerLayer, type MarkerLayerOptions, type LineLayer, type LineLayerOptions, type UserLocationOptions, } from "./plugins/gen/map/sdk/map";
|
|
72
73
|
export { Push, type PushPayload, type PushStatus, type PushRegisterOptions, type PushEvent } from "./plugins/gen/push/sdk/push";
|
|
73
|
-
export { OAuth, type OAuthCredential, type OAuthProviderName } from "./plugins/oauth";
|
|
74
74
|
export { Service } from "./plugins/service";
|
|
75
75
|
export { defineScene, use, ref, make, SceneHandle } from "./scene/defineScene";
|
|
76
76
|
export { defineMaterial, MaterialHandle } from "./scene/material";
|
|
@@ -3,6 +3,7 @@ export type Field = {
|
|
|
3
3
|
key: string;
|
|
4
4
|
kind: FieldKind;
|
|
5
5
|
len: number;
|
|
6
|
+
step: boolean;
|
|
6
7
|
};
|
|
7
8
|
export type Layout = {
|
|
8
9
|
fields: Field[];
|
|
@@ -11,7 +12,7 @@ export type Layout = {
|
|
|
11
12
|
bytes: number;
|
|
12
13
|
sig: string;
|
|
13
14
|
};
|
|
14
|
-
export declare const makeLayout: (defaults: Record<string, unknown>, what: string) => Layout;
|
|
15
|
+
export declare const makeLayout: (defaults: Record<string, unknown>, what: string, discrete?: readonly string[]) => Layout;
|
|
15
16
|
/** A fresh state object shaped like the defaults (arrays copied, never shared). */
|
|
16
17
|
export declare const cloneDefaults: <T extends object>(defaults: T) => T;
|
|
17
18
|
/** Copy the layout's fields from `from` into `into` (array elements copied in place; unknown keys
|
|
@@ -19,7 +20,7 @@ export declare const cloneDefaults: <T extends object>(defaults: T) => T;
|
|
|
19
20
|
export declare const assignState: (layout: Layout, into: any, from: any) => void;
|
|
20
21
|
export declare const packInto: (view: DataView, offset: number, layout: Layout, s: any) => number;
|
|
21
22
|
export declare const unpackFrom: (view: DataView, offset: number, layout: Layout, into: any) => number;
|
|
22
|
-
/** `out = a + (b − a) · t` for the numeric fields; booleans take `b`'s. */
|
|
23
|
+
/** `out = a + (b − a) · t` for the numeric fields; booleans and the discrete ones take `b`'s. */
|
|
23
24
|
export declare const lerpState: (layout: Layout, a: any, b: any, t: number, out: any) => void;
|
|
24
25
|
/** FNV-1a over a string → 8 hex chars. Enough to tell two bundles' schemas apart. */
|
|
25
26
|
export declare const hashString: (s: string) => string;
|
package/dist/types/net/core.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type NetStats = {
|
|
|
34
34
|
};
|
|
35
35
|
export declare const CH_RELIABLE = 0;
|
|
36
36
|
export declare const CH_UNRELIABLE = 1;
|
|
37
|
-
export declare const PROTO =
|
|
37
|
+
export declare const PROTO = 3;
|
|
38
38
|
export declare const LOCAL_SLOT = -1;
|
|
39
39
|
export declare const bridge: () => typeof _creatorNet | null;
|
|
40
40
|
export declare class NetPlayer {
|
|
@@ -45,6 +45,9 @@ export declare class NetPlayer {
|
|
|
45
45
|
/** A local bag for game facts (score, team, ready). NOT replicated — anything a client must see
|
|
46
46
|
* goes through a message or replicated state. */
|
|
47
47
|
data: any;
|
|
48
|
+
/** Server: what this player's `Net.connect(…, { hello })` carried (the game's own: a build, a
|
|
49
|
+
* token) — the value `Net.listen({ accept })` was asked about. Undefined everywhere else. */
|
|
50
|
+
hello: unknown;
|
|
48
51
|
private _leave;
|
|
49
52
|
constructor(id: number, name: string, slot: number);
|
|
50
53
|
get isMe(): boolean;
|
|
@@ -72,6 +75,12 @@ export declare const state: {
|
|
|
72
75
|
connectResolve: null | (() => void);
|
|
73
76
|
connectReject: null | ((e: Error) => void);
|
|
74
77
|
myName: string;
|
|
78
|
+
/** client: the game's own part of the `$hello` (`Net.connect(…, { hello })`), opaque to the SDK */
|
|
79
|
+
hello: unknown;
|
|
80
|
+
/** server: the game's say in the handshake (`Net.listen({ accept })`) — a reason refuses */
|
|
81
|
+
accept: null | ((hello: unknown, who: {
|
|
82
|
+
name: string;
|
|
83
|
+
}) => string | null | void);
|
|
75
84
|
};
|
|
76
85
|
export declare const hooks: {
|
|
77
86
|
frameEarly: null | ((dt: number) => void);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NetPlayer, type NetEvents, type NetLaunch, type NetMessage, type NetRole, type NetStatus } from "./core";
|
|
2
|
-
import { NetEntity, Replicated, stats, type NetInput, type NetKind, type NetTransform } from "./replication";
|
|
2
|
+
import { NetEntity, Replicated, stats, type NetCorrection, type NetInput, type NetKind, type NetTransform } from "./replication";
|
|
3
3
|
export { NetPlayer, NetEntity, Replicated };
|
|
4
|
-
export type { NetEvents, NetLaunch, NetMessage, NetRole, NetStatus, NetInput, NetKind, NetTransform };
|
|
4
|
+
export type { NetEvents, NetLaunch, NetMessage, NetRole, NetStatus, NetInput, NetKind, NetTransform, NetCorrection };
|
|
5
5
|
declare class NetImpl {
|
|
6
6
|
get role(): NetRole;
|
|
7
7
|
get status(): NetStatus;
|
|
@@ -19,21 +19,33 @@ declare class NetImpl {
|
|
|
19
19
|
/** The simulation tick — one per fixed step (`updateFixed`). The server's is THE tick; a client
|
|
20
20
|
* runs a few ticks ahead so its inputs arrive in time. */
|
|
21
21
|
get tick(): number;
|
|
22
|
+
/** The server tick this machine is SHOWING the others at: on a client, its estimate of the server's
|
|
23
|
+
* clock less the interpolation delay (fractional); `tick` everywhere else, where nothing is
|
|
24
|
+
* interpolated. What a client names when it tells the server "as I saw it". */
|
|
25
|
+
get viewTick(): number;
|
|
22
26
|
/** Live counters: snapshots, input lead / `ahead`, corrections, missed inputs, bytes out. */
|
|
23
27
|
get stats(): typeof stats;
|
|
24
28
|
/** Start serving on `port`. From offline this makes us the host (a player who is also the
|
|
25
|
-
* server); on the dedicated exe (`Net.launch.role === "server"`) there is no local player.
|
|
29
|
+
* server); on the dedicated exe (`Net.launch.role === "server"`) there is no local player.
|
|
30
|
+
* `accept` is the game's say in the handshake: it is given what the client's `connect` passed as
|
|
31
|
+
* `hello` and returns a reason to refuse (the client's `reject` event carries it), nothing to accept. */
|
|
26
32
|
listen(opts?: {
|
|
27
33
|
port?: number;
|
|
28
34
|
maxClients?: number;
|
|
29
35
|
name?: string;
|
|
30
36
|
dedicated?: boolean;
|
|
37
|
+
accept?: (hello: unknown, who: {
|
|
38
|
+
name: string;
|
|
39
|
+
}) => string | null | void;
|
|
31
40
|
}): boolean;
|
|
32
|
-
/** Connect to a host/server (`address` = "ip:port"). Resolves once the server welcomed us.
|
|
41
|
+
/** Connect to a host/server (`address` = "ip:port"). Resolves once the server welcomed us.
|
|
42
|
+
* `hello` = anything JSON the game wants the server to see before it lets us in (its
|
|
43
|
+
* `Net.listen({ accept })`, then `player.hello`); the SDK only carries it. */
|
|
33
44
|
connect(target: {
|
|
34
45
|
address: string;
|
|
35
46
|
}, opts?: {
|
|
36
47
|
name?: string;
|
|
48
|
+
hello?: unknown;
|
|
37
49
|
}): Promise<void>;
|
|
38
50
|
/** Client → offline. Host/server → everyone is dropped, back to offline. */
|
|
39
51
|
disconnect(): void;
|
|
@@ -62,10 +74,15 @@ declare class NetImpl {
|
|
|
62
74
|
setInput<T extends object>(inp: NetInput<T>, values: Partial<T>): void;
|
|
63
75
|
/** A replicated entity type: the defaults are the snapshot layout (same field rules as `input`),
|
|
64
76
|
* `transform` tells the SDK which fields mirror the node's pose, `client` births the proxy on a
|
|
65
|
-
* client when the server spawns one (`new Node().aspect(Replicated, { kind, owner })`)
|
|
77
|
+
* client when the server spawns one (`new Node().aspect(Replicated, { kind, owner })`) — it may
|
|
78
|
+
* be set later, `Kind.client = …`, where the client's code lives. `discrete` names the numeric
|
|
79
|
+
* fields that are never interpolated (an index, a count); `correction` tunes how the owner's
|
|
80
|
+
* prediction is brought back to the server's state (all in the game's own units). */
|
|
66
81
|
kind<S extends object>(name: string, defaults: S, opts?: {
|
|
67
82
|
transform?: NetTransform;
|
|
68
83
|
client?: (rep: NetEntity<S>, scene: any) => any;
|
|
84
|
+
discrete?: readonly (keyof S & string)[];
|
|
85
|
+
correction?: Partial<NetCorrection>;
|
|
69
86
|
}): NetKind<S>;
|
|
70
87
|
/** Every replicated entity this machine knows. */
|
|
71
88
|
get entities(): readonly NetEntity[];
|
|
@@ -5,6 +5,18 @@ import { Scene } from "../gl/Scene";
|
|
|
5
5
|
import { type Layout } from "./codec";
|
|
6
6
|
import { NetPlayer } from "./core";
|
|
7
7
|
export type NetTransform = "pos" | "pos+rot" | "none";
|
|
8
|
+
/** How an owner's prediction is brought back to the server's state. The SDK knows nothing of what
|
|
9
|
+
* the entity is, so the defaults are neutral: every disagreement counts, none is a teleport. */
|
|
10
|
+
export type NetCorrection = {
|
|
11
|
+
/** A vertical disagreement below this is ignored (world units). 0 = none ignored. A body that
|
|
12
|
+
* snaps to a floor differs by a little between two machines, and lifting it by that feeds itself. */
|
|
13
|
+
deadbandY: number;
|
|
14
|
+
/** A disagreement of this much or more is a teleport: the body jumps and `correction` stays zero
|
|
15
|
+
* (world units). Infinity = always smoothed. */
|
|
16
|
+
snap: number;
|
|
17
|
+
/** The decay time constant of the visual offset `correction` (seconds). 0 = no smoothing. */
|
|
18
|
+
tau: number;
|
|
19
|
+
};
|
|
8
20
|
/** The per-tick input layout: `const PlayerInput = Net.input({ moveX: 0, moveY: 0, jump: false })`. */
|
|
9
21
|
export type NetInput<T extends object = any> = {
|
|
10
22
|
readonly layout: Layout;
|
|
@@ -19,8 +31,10 @@ export type NetKind<S extends object = any> = {
|
|
|
19
31
|
/** Which state fields the SDK samples on the server and applies to a client proxy: `pos` (a
|
|
20
32
|
* `pos: [x,y,z]` field), `pos+rot` (+ a `rot: [x,y,z,w]` quaternion field), or `none`. */
|
|
21
33
|
readonly transform: NetTransform;
|
|
22
|
-
|
|
23
|
-
|
|
34
|
+
readonly correction: NetCorrection;
|
|
35
|
+
/** Births the client-side proxy on spawn; the SDK attaches `Replicated` to what it returns.
|
|
36
|
+
* Assignable: a declaration both ends share may leave it to the client's own module. */
|
|
37
|
+
client?: (rep: NetEntity<S>, scene: Scene | null) => Node | void;
|
|
24
38
|
readonly __t?: S;
|
|
25
39
|
};
|
|
26
40
|
/** Live counters (`Net.stats`): how the clocks and the prediction are doing. */
|
|
@@ -59,6 +73,9 @@ export declare class NetEntity<S extends object = any> {
|
|
|
59
73
|
/** The owner's input for the tick being simulated — the ring entry on the server (the last one
|
|
60
74
|
* repeated when it is late), the local input on the owner / host / offline. */
|
|
61
75
|
input<T extends object>(inp: NetInput<T>): T;
|
|
76
|
+
/** `input()` is an input the owner gave — false until the first one exists (the server before
|
|
77
|
+
* the owner's first packet, the owner before its first `Net.setInput`), when it is the defaults. */
|
|
78
|
+
get hasInput(): boolean;
|
|
62
79
|
/** Client: `state` was refreshed (a snapshot landed / the interpolation moved). */
|
|
63
80
|
on(event: "state", cb: (s: S) => void): this;
|
|
64
81
|
off(event: "state", cb: (s: S) => void): this;
|
|
@@ -66,10 +83,13 @@ export declare class NetEntity<S extends object = any> {
|
|
|
66
83
|
export declare const declareKind: <S extends object>(name: string, defaults: S, opts?: {
|
|
67
84
|
transform?: NetTransform;
|
|
68
85
|
client?: NetKind<S>["client"];
|
|
86
|
+
discrete?: readonly string[];
|
|
87
|
+
correction?: Partial<NetCorrection>;
|
|
69
88
|
}) => NetKind<S>;
|
|
70
89
|
export declare const declareInput: <T extends object>(defaults: T) => NetInput<T>;
|
|
71
90
|
export declare const setInput: <T extends object>(inp: NetInput<T>, values: Partial<T>) => void;
|
|
72
91
|
export declare const currentTick: () => number;
|
|
92
|
+
export declare const viewTick: () => number;
|
|
73
93
|
export declare const allEntities: () => NetEntity[];
|
|
74
94
|
export declare const entityById: (netId: number) => NetEntity | undefined;
|
|
75
95
|
/**
|
|
@@ -97,5 +117,6 @@ export declare class Replicated extends Aspect<"replicated", Node> {
|
|
|
97
117
|
get correction(): Vec3;
|
|
98
118
|
/** The owner's input for the tick being simulated (see `NetEntity.input`). */
|
|
99
119
|
input<T extends object>(inp: NetInput<T>): T;
|
|
120
|
+
get hasInput(): boolean;
|
|
100
121
|
}
|
|
101
122
|
export declare const installReplication: () => void;
|
|
@@ -56,6 +56,13 @@ export declare const device: {
|
|
|
56
56
|
* feels native on each platform; see {@link HapticStyle}. Host-gated: a silent no-op where there's
|
|
57
57
|
* no haptic hardware (iPad, older iPhones, web, headless). */
|
|
58
58
|
vibrate(style?: HapticStyle): void;
|
|
59
|
+
/** The host's performance overlay: one line over everything with the frames per second the
|
|
60
|
+
* display actually got, how evenly they came (`66 fps · 73% even` is judder, `60 fps · 100% even`
|
|
61
|
+
* is smooth), and the GPU's and the main thread's milliseconds per frame. The host measures and
|
|
62
|
+
* draws it itself about once a second — nothing of it runs in the app's JS, so it does not change
|
|
63
|
+
* what it measures. It belongs to the host, not to the project: it stays on when another project
|
|
64
|
+
* is opened. Reads `false`, and writing does nothing, on hosts without one (web, headless). */
|
|
65
|
+
statsOverlay: boolean;
|
|
59
66
|
/** Device-orientation sensor (gyro + accelerometer, fused) for tilt/steering and magic-window /
|
|
60
67
|
* 360° panoramas. Poll `attitude` / `gravity` inside setLoop; they return the freshest fused
|
|
61
68
|
* sample, so the sensor rate need not match your frame rate. Host-gated: a no-op with no sensor. */
|
|
@@ -10,15 +10,13 @@
|
|
|
10
10
|
* Wire (implemented by the runner):
|
|
11
11
|
* POST <serverUrl>/api/<id> body {"args":[…]}, `authorization: Bearer <session>` when known
|
|
12
12
|
* → 200 {"ok":true,"result":…,"session"?:"…"} | {"ok":false,"status":n,"message":"…","session"?:"…"}
|
|
13
|
-
* WS <serverUrl>/ws
|
|
14
|
-
* ← {t:"session",session} {t:"ok",id} {t:"err",id,status,message} {t:"ev",ch,topic,event,data}
|
|
15
|
-
* (`ev` is addressed by channel + topic, not by subscription id, so the server can fan one publish out
|
|
16
|
-
* to every socket of a topic natively; the client delivers it to each matching subscription.)
|
|
13
|
+
* WS <serverUrl>/ws the channel socket — its frames are ./wire.ts
|
|
17
14
|
*
|
|
18
15
|
* The session token is transport-owned (cookie-like): stored under `lecodes.session:<serverUrl>` in
|
|
19
16
|
* localStorage, sent on every call and on the socket, replaced whenever a response carries `session`.
|
|
20
17
|
* User code never sees it — "am I logged in" is an endpoint (`me()`), see the plan.
|
|
21
18
|
*/
|
|
19
|
+
import type { ChannelSubscription } from "../server/channel";
|
|
22
20
|
/** Rejection value of a failed endpoint call: `status` mirrors the server's `ApiError`
|
|
23
21
|
* (0 = the request itself failed: no network / no server). */
|
|
24
22
|
export declare class RpcError extends Error {
|
|
@@ -29,16 +27,12 @@ export declare class RpcError extends Error {
|
|
|
29
27
|
export declare const __rpc: (serverUrl: string, id: string) => (...args: unknown[]) => Promise<any>;
|
|
30
28
|
/** A server-only export (db, models, helpers) referenced from client code: any use throws. */
|
|
31
29
|
export declare const __serverOnly: (id: string) => any;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
/** Build the client proxy of channel `id`. */
|
|
44
|
-
export declare const __channel: (serverUrl: string, id: string) => ClientChannel<any>;
|
|
30
|
+
/**
|
|
31
|
+
* Build the client proxy of channel `id` — what a `channel()` export of a `*.server.ts` file is in
|
|
32
|
+
* the app (its type is the server's: src/server/channel.ts). `subscribe([...args,] handler, options?)`:
|
|
33
|
+
* what stands before the handler travels to the channel's `groupBy`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const __channel: (serverUrl: string, id: string) => {
|
|
36
|
+
subscribe(...all: unknown[]): ChannelSubscription;
|
|
37
|
+
publish(): never;
|
|
38
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The frames of the channel socket (`<serverUrl>/ws`) — the ONE description both ends are written
|
|
3
|
+
* against: the client transport (./rpc.ts) and the server's hub (../server/channelHub.ts, which the
|
|
4
|
+
* runner and the local backend of `lecodes dev` both run). JSON text frames, one per message.
|
|
5
|
+
*
|
|
6
|
+
* → hello who is on this socket. Sent first, and again whenever the session changes: the server
|
|
7
|
+
* then DROPS the socket's subscriptions (they were granted to someone else) and the
|
|
8
|
+
* client sends them again, under new ids.
|
|
9
|
+
* → sub subscribe to channel `ch`; `args` are what the app passed to `subscribe` before the
|
|
10
|
+
* handler — the channel's `groupBy` reads them.
|
|
11
|
+
* → unsub
|
|
12
|
+
* ← session a token minted while the socket was served (the first contact of a guest): the client
|
|
13
|
+
* stores it; nothing is dropped, the server already has it.
|
|
14
|
+
* ← ok the subscription stands; `group` is where the server put it (null = a channel without
|
|
15
|
+
* groups). The client learns its group only here — it never names one.
|
|
16
|
+
* ← err refused (a hook threw) — the subscription is gone.
|
|
17
|
+
* ← ev one published message, addressed by channel + group so the server sends one frame to
|
|
18
|
+
* every socket of a group; the client hands it to each of its subscriptions there.
|
|
19
|
+
*
|
|
20
|
+
* A group's value is not a secret: its subscriber is told it.
|
|
21
|
+
*/
|
|
22
|
+
/** What `groupBy` may answer. `42` and `"42"` are two groups. */
|
|
23
|
+
export type ChannelGroup = string | number;
|
|
24
|
+
export type ClientFrame = {
|
|
25
|
+
t: "hello";
|
|
26
|
+
session: string | null;
|
|
27
|
+
} | {
|
|
28
|
+
t: "sub";
|
|
29
|
+
id: number;
|
|
30
|
+
ch: string;
|
|
31
|
+
args: unknown[];
|
|
32
|
+
} | {
|
|
33
|
+
t: "unsub";
|
|
34
|
+
id: number;
|
|
35
|
+
};
|
|
36
|
+
export type ServerFrame = {
|
|
37
|
+
t: "session";
|
|
38
|
+
session: string;
|
|
39
|
+
} | {
|
|
40
|
+
t: "ok";
|
|
41
|
+
id: number;
|
|
42
|
+
group: ChannelGroup | null;
|
|
43
|
+
} | {
|
|
44
|
+
t: "err";
|
|
45
|
+
id: number;
|
|
46
|
+
status: number;
|
|
47
|
+
message: string;
|
|
48
|
+
} | {
|
|
49
|
+
t: "ev";
|
|
50
|
+
ch: string;
|
|
51
|
+
group: ChannelGroup | null;
|
|
52
|
+
data: unknown;
|
|
53
|
+
};
|