@driftengine/script 3.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +9 -0
  3. package/README.md +81 -0
  4. package/dist/bindings/ai.d.ts +66 -0
  5. package/dist/bindings/ai.js +173 -0
  6. package/dist/bindings/animation.d.ts +13 -0
  7. package/dist/bindings/animation.js +177 -0
  8. package/dist/bindings/audio.d.ts +56 -0
  9. package/dist/bindings/audio.js +126 -0
  10. package/dist/bindings/behavior.d.ts +36 -0
  11. package/dist/bindings/behavior.js +101 -0
  12. package/dist/bindings/chemistry.d.ts +72 -0
  13. package/dist/bindings/chemistry.js +253 -0
  14. package/dist/bindings/core.d.ts +172 -0
  15. package/dist/bindings/core.js +814 -0
  16. package/dist/bindings/editor.d.ts +53 -0
  17. package/dist/bindings/editor.js +248 -0
  18. package/dist/bindings/entities.d.ts +73 -0
  19. package/dist/bindings/entities.js +339 -0
  20. package/dist/bindings/interface.d.ts +55 -0
  21. package/dist/bindings/interface.js +200 -0
  22. package/dist/bindings/navigation.d.ts +36 -0
  23. package/dist/bindings/navigation.js +255 -0
  24. package/dist/bindings/network.d.ts +51 -0
  25. package/dist/bindings/network.js +96 -0
  26. package/dist/bindings/render.d.ts +71 -0
  27. package/dist/bindings/render.js +143 -0
  28. package/dist/bindings/rollback.d.ts +41 -0
  29. package/dist/bindings/rollback.js +74 -0
  30. package/dist/bindings/sprites.d.ts +53 -0
  31. package/dist/bindings/sprites.js +186 -0
  32. package/dist/bindings/terrain.d.ts +54 -0
  33. package/dist/bindings/terrain.js +138 -0
  34. package/dist/bindings/xr.d.ts +56 -0
  35. package/dist/bindings/xr.js +155 -0
  36. package/dist/entityHost.d.ts +80 -0
  37. package/dist/entityHost.js +159 -0
  38. package/dist/host.d.ts +153 -0
  39. package/dist/host.js +233 -0
  40. package/dist/index.d.ts +21 -0
  41. package/dist/index.js +18 -0
  42. package/package.json +68 -0
  43. package/src/bindings/ai.ts +306 -0
  44. package/src/bindings/animation.ts +346 -0
  45. package/src/bindings/audio.ts +185 -0
  46. package/src/bindings/behavior.ts +181 -0
  47. package/src/bindings/chemistry.ts +548 -0
  48. package/src/bindings/core.ts +1604 -0
  49. package/src/bindings/editor.ts +393 -0
  50. package/src/bindings/entities.ts +550 -0
  51. package/src/bindings/interface.ts +300 -0
  52. package/src/bindings/navigation.ts +367 -0
  53. package/src/bindings/network.ts +178 -0
  54. package/src/bindings/render.ts +191 -0
  55. package/src/bindings/rollback.ts +110 -0
  56. package/src/bindings/sprites.ts +310 -0
  57. package/src/bindings/terrain.ts +180 -0
  58. package/src/bindings/xr.ts +272 -0
  59. package/src/entityHost.ts +219 -0
  60. package/src/host.ts +465 -0
  61. package/src/index.ts +41 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * `drift/xr` — what a script may ask about a headset, and what it may not.
3
+ *
4
+ * **The last surface the linker refused, and it refused it since 1.0.** `drift/xr` has been in the
5
+ * language's `SPECIFIED_MODULES` the whole time with nothing here describing it, which is exactly
6
+ * the arrangement the language exists to make possible: a file naming it parsed, type-checked and
7
+ * declined only at link time, and the same file links now without a character changing.
8
+ *
9
+ * ## Nothing here is deterministic, and that is the property
10
+ *
11
+ * A head moves because somebody moved their head. A controller reports where it is because a camera
12
+ * saw it. None of that is a function of the simulation, so none of it may be read from inside the
13
+ * fixed step: a `@deterministic` system branching on where a hand is would take the other branch on
14
+ * replay, and a stored run would stop meaning anything.
15
+ *
16
+ * `drift/ui` settled the same question for a pointer, and the reasoning transfers unchanged: its
17
+ * `hovered` and `pressed` are fields on a tree that `scene.read` would have type-checked and would
18
+ * have been a lie about what they carry, so they were declared `input.read` and left outside
19
+ * `DETERMINISTIC_EFFECTS`. A head and a hand are the same kind of fact arriving through the same
20
+ * door.
21
+ *
22
+ * ## No matrices, and the argument is `drift/render`'s
23
+ *
24
+ * There is no `xr.projection` and no `xr.viewMatrix`. An eye's projection is a fact about somebody's
25
+ * optics, and its view is a fact about where their head is in a room the script has never seen;
26
+ * handing either to a script would be `drift/render`'s refused dial read in a different costume, a
27
+ * machine-dependent number registered where a script could compare two runs with it.
28
+ *
29
+ * What a script gets is where the head is, whether a session is running, and what the hands are
30
+ * doing. Drawing the two eyes is the host's, because the host owns the renderer and the loop.
31
+ *
32
+ * ## `presenting` is `scene.read` and the rest is `input.read`
33
+ *
34
+ * Whether a session is running is a fact about the view: it decides what is drawn and where, and it
35
+ * changes when somebody puts a headset on. `scene.read` sits outside `DETERMINISTIC_EFFECTS` for
36
+ * that reason, which is the one this needs.
37
+ */
38
+ import { defineCapability } from 'driftscript';
39
+ import { JOINT_INDEX } from '@driftengine/xr';
40
+ export const XR_MODULE = 'drift/xr';
41
+ export const XR_TYPES = [];
42
+ const define = (name, params, returns, effects, doc, implementation) => defineCapability({
43
+ module: XR_MODULE,
44
+ name,
45
+ signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> ${returns}`,
46
+ params: [...params],
47
+ returns,
48
+ effects,
49
+ /*
50
+ * Never deterministic, on every capability here. See the header: a head moves because a person
51
+ * moved, and a `@deterministic` system reading one would take a different branch on replay.
52
+ */
53
+ deterministic: false,
54
+ doc,
55
+ implementation,
56
+ });
57
+ /** `left`, `right`, or anything else for the source with no handedness. */
58
+ const HAND = { name: 'hand', type: 'String' };
59
+ export const XR_CAPABILITIES = [
60
+ define('presenting', [], 'bool', ['scene.read'], 'Whether a session is running. False before one starts and after the user takes the headset off.', 'XrRuntime.presenting'),
61
+ define('supported', [], 'bool', ['scene.read'], 'Whether this device could present at all. False on a machine with no headset attached.', 'XrRuntime.supported'),
62
+ /*
63
+ * The head, as three reads rather than one call returning a triple.
64
+ *
65
+ * The language has no tuple and no out parameter, and a `data` record would be a value the host
66
+ * allocates per call inside a frame loop. Three floats is the shape every other position on this
67
+ * surface takes, and `drift/navigation`'s `steerX`/`steerY`/`steerZ` set the precedent.
68
+ */
69
+ /*
70
+ * `f32` and not `float`. The language refuses a width-polymorphic return with no `float`
71
+ * parameter to fix it against, which `drift/navigation.steerX` never meets because it takes the
72
+ * position it steers from. These take a hand or nothing, so the width is stated: every position
73
+ * this engine stores is a `Float32Array`, and answering `f64` would widen a number that was
74
+ * never that precise.
75
+ */
76
+ define('headX', [], 'f32', ['input.read'], 'Where the viewer’s head is, in metres. Zero outside a session.', 'XrRuntime.headX'),
77
+ define('headY', [], 'f32', ['input.read'], 'Where the viewer’s head is, in metres. Zero outside a session.', 'XrRuntime.headY'),
78
+ define('headZ', [], 'f32', ['input.read'], 'Where the viewer’s head is, in metres. Zero outside a session.', 'XrRuntime.headZ'),
79
+ define('trigger', [HAND], 'f32', ['input.read'], 'How far a hand’s trigger is pulled, 0 to 1. Zero for a hand that is not there.', 'XrRuntime.trigger'),
80
+ define('squeeze', [HAND], 'f32', ['input.read'], 'How far a hand’s grip is squeezed, 0 to 1. Zero for a hand that is not there.', 'XrRuntime.squeeze'),
81
+ define('holding', [HAND], 'bool', ['input.read'], 'Whether a hand is tracked this frame. False while a controller is set down or out of view.', 'XrRuntime.holding'),
82
+ /*
83
+ * A joint by name and not by index.
84
+ *
85
+ * `HAND_JOINTS` fixes an order and a script could be handed it, and should not be: an index is a
86
+ * number a script would have to keep its own copy of and watch drift against a specification.
87
+ * A name is checked at the call and answers false for one nobody has heard of.
88
+ */
89
+ define('jointX', [HAND, { name: 'joint', type: 'String' }], 'f32', ['input.read'], 'Where a named hand joint is, in metres. Zero for a joint that is not tracked.', 'XrRuntime.jointX'),
90
+ define('jointY', [HAND, { name: 'joint', type: 'String' }], 'f32', ['input.read'], 'Where a named hand joint is, in metres. Zero for a joint that is not tracked.', 'XrRuntime.jointY'),
91
+ define('jointZ', [HAND, { name: 'joint', type: 'String' }], 'f32', ['input.read'], 'Where a named hand joint is, in metres. Zero for a joint that is not tracked.', 'XrRuntime.jointZ'),
92
+ define('pinching', [HAND], 'bool', ['input.read'], 'Whether a hand’s thumb and index tips are close enough to count as a pinch.', 'XrRuntime.pinching'),
93
+ ];
94
+ /** How close two fingertips count as a pinch, in metres. */
95
+ const PINCH_METRES = 0.025;
96
+ function handOf(value) {
97
+ return value === 'left' ? 'left' : value === 'right' ? 'right' : 'none';
98
+ }
99
+ export function xrImplementation(runtime) {
100
+ const at = new Float32Array(3);
101
+ const other = new Float32Array(3);
102
+ const head = (axis) => runtime.headPosition?.[axis] ?? 0;
103
+ const joint = (hand, name, axis) => {
104
+ const skeleton = runtime.hand(handOf(hand));
105
+ if (skeleton === null)
106
+ return 0;
107
+ /* The flat arrays the package already fills are read directly. `jointPosition` would answer the
108
+ same question through a `Float32Array` this would then have to unpack, three times per joint
109
+ per frame. */
110
+ const index = jointIndexOf(name);
111
+ if (index < 0 || skeleton.tracked[index] !== 1)
112
+ return 0;
113
+ return skeleton.matrices[index * 16 + 12 + axis] ?? 0;
114
+ };
115
+ return {
116
+ presenting: () => runtime.headPosition !== null,
117
+ supported: () => runtime.support?.immersiveVr === true || runtime.support?.immersiveAr === true,
118
+ headX: () => head(0),
119
+ headY: () => head(1),
120
+ headZ: () => head(2),
121
+ trigger: (hand) => runtime.controller(handOf(hand))?.buttons[0] ?? 0,
122
+ squeeze: (hand) => runtime.controller(handOf(hand))?.buttons[1] ?? 0,
123
+ holding: (hand) => runtime.controller(handOf(hand))?.tracked ?? false,
124
+ jointX: (hand, name) => joint(hand, name, 0),
125
+ jointY: (hand, name) => joint(hand, name, 1),
126
+ jointZ: (hand, name) => joint(hand, name, 2),
127
+ pinching: (hand) => {
128
+ const skeleton = runtime.hand(handOf(hand));
129
+ if (skeleton === null)
130
+ return false;
131
+ const thumb = jointIndexOf('thumb-tip');
132
+ const index = jointIndexOf('index-finger-tip');
133
+ if (skeleton.tracked[thumb] !== 1 || skeleton.tracked[index] !== 1)
134
+ return false;
135
+ for (let i = 0; i < 3; i++) {
136
+ at[i] = skeleton.matrices[thumb * 16 + 12 + i] ?? 0;
137
+ other[i] = skeleton.matrices[index * 16 + 12 + i] ?? 0;
138
+ }
139
+ const dx = at[0] - other[0];
140
+ const dy = at[1] - other[1];
141
+ const dz = at[2] - other[2];
142
+ return Math.sqrt(dx * dx + dy * dy + dz * dz) <= PINCH_METRES;
143
+ },
144
+ };
145
+ }
146
+ /**
147
+ * A joint's index by name.
148
+ *
149
+ * **Read from `@driftengine/xr` rather than written again here.** That package owns the order, an
150
+ * index is written into recordings, and a second copy of the list would be a second answer to what
151
+ * a stored hand means the first time somebody added a joint to one of them.
152
+ */
153
+ function jointIndexOf(name) {
154
+ return JOINT_INDEX[name] ?? -1;
155
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Building a world from what a module declares.
3
+ *
4
+ * A compiled `.drs` file carries its components, entities, systems and prefabs as **description**
5
+ * rather than code. This turns that description into the engine's own objects: a `ComponentType`
6
+ * per component, a `Prefab` per prefab, and a `SystemDefinition` per system.
7
+ *
8
+ * ## A system definition calls through `exports`, and that is what makes it hot-reloadable
9
+ *
10
+ * `runSchedule` caches one `BoundSystem` per world and definition, and calls `definition.run(view)`.
11
+ * A definition holding the generated function *directly* would pin the pre-patch body in the
12
+ * schedule forever — and the symptom is an edit that compiles, reloads, reports success and changes
13
+ * nothing, which is the one failure hot reload exists to prevent.
14
+ *
15
+ * So `run` reaches the function through `module.exports` on every tick. That is one property lookup
16
+ * per system per tick, which is the same bargain `runtime/module.ts` documents at its own
17
+ * declaration and the reason that indirection exists at all.
18
+ *
19
+ * ## A host-declared component is asserted, never created
20
+ *
21
+ * `component Transform from host { … }` says the host registered one and this is its shape. The
22
+ * module creates nothing for it; it is checked when the module binds, and a mismatch is refused
23
+ * naming the field and both types. **The refusal is the whole value of the declaration** — without
24
+ * it the two descriptions drift, and the first thing anybody notices is a field reading zeroes.
25
+ *
26
+ * ## A system's resources are resolved here, once, and `@driftengine/entities` never learns about them
27
+ *
28
+ * `system Walk { uses graph: NavGraph … }` asks to be handed a value this host owns. Without it a
29
+ * handle reaches a script only as a *function* parameter, and every per-entity walk has to be
30
+ * driven from TypeScript. The generated function takes a second parameter: a table keyed by **type
31
+ * name**, so a host supplies "the `NavGraph` this world has" and never learns what any script
32
+ * called its binding.
33
+ *
34
+ * The table is built at registration and captured, rather than assembled per tick. A resource does
35
+ * not change between ticks, and an object per system per tick is the allocation this package refuses
36
+ * everywhere else.
37
+ *
38
+ * **A missing resource is refused at registration, not left as `undefined`.** Passing nothing would
39
+ * reach a capability as an absent argument and produce a wrong *result* rather than an error, which
40
+ * is the trap `host.ts` step four is about. Init is where a person is watching.
41
+ *
42
+ * `SystemDefinition` gains nothing for this. A resource is a host's object, and the ECS has no
43
+ * business knowing one exists — so the table lives in the closure that already reaches through
44
+ * `module.exports`.
45
+ */
46
+ import { type ComponentType, type Prefab, type SystemDefinition } from '@driftengine/entities';
47
+ import type { DriftModule } from 'driftscript';
48
+ /** Components a host already registered, by the name a script writes. */
49
+ export type ComponentRegistry = Map<string, ComponentType>;
50
+ /**
51
+ * Values a host lends to systems, by the **type name** a script writes in a `uses` clause.
52
+ *
53
+ * By type rather than by binding name, which is the language's own rule: a resource is one per type,
54
+ * so two systems asking for a `NavGraph` are handed the same object whatever each file calls it, and
55
+ * a script renaming its binding changes nothing here.
56
+ */
57
+ export type ResourceRegistry = ReadonlyMap<string, unknown>;
58
+ export interface RegisteredModule {
59
+ /** Component types this module declared, in declaration order. Host-asserted ones are absent. */
60
+ readonly components: readonly ComponentType[];
61
+ readonly prefabs: readonly Prefab[];
62
+ /** Ready for `buildSchedule`, in declaration order. */
63
+ readonly systems: readonly SystemDefinition[];
64
+ }
65
+ /**
66
+ * Check every `from host` declaration against what the host registered.
67
+ *
68
+ * Throws on the first mismatch rather than collecting: a shape disagreement means the module was
69
+ * compiled against a different world than the one it is being loaded into, and the second error
70
+ * would be a consequence of the first.
71
+ */
72
+ export declare function assertHostShapes(module: DriftModule, registry: ComponentRegistry): void;
73
+ /**
74
+ * Build every component, prefab and system a module declares.
75
+ *
76
+ * Components are registered into `registry` as they are made, so a prefab or a system later in the
77
+ * same module finds them — and so a second module loaded against the same registry shares them
78
+ * rather than making a second store for one name.
79
+ */
80
+ export declare function registerEntityModule(module: DriftModule, registry: ComponentRegistry, resources?: ResourceRegistry): RegisteredModule;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Building a world from what a module declares.
3
+ *
4
+ * A compiled `.drs` file carries its components, entities, systems and prefabs as **description**
5
+ * rather than code. This turns that description into the engine's own objects: a `ComponentType`
6
+ * per component, a `Prefab` per prefab, and a `SystemDefinition` per system.
7
+ *
8
+ * ## A system definition calls through `exports`, and that is what makes it hot-reloadable
9
+ *
10
+ * `runSchedule` caches one `BoundSystem` per world and definition, and calls `definition.run(view)`.
11
+ * A definition holding the generated function *directly* would pin the pre-patch body in the
12
+ * schedule forever — and the symptom is an edit that compiles, reloads, reports success and changes
13
+ * nothing, which is the one failure hot reload exists to prevent.
14
+ *
15
+ * So `run` reaches the function through `module.exports` on every tick. That is one property lookup
16
+ * per system per tick, which is the same bargain `runtime/module.ts` documents at its own
17
+ * declaration and the reason that indirection exists at all.
18
+ *
19
+ * ## A host-declared component is asserted, never created
20
+ *
21
+ * `component Transform from host { … }` says the host registered one and this is its shape. The
22
+ * module creates nothing for it; it is checked when the module binds, and a mismatch is refused
23
+ * naming the field and both types. **The refusal is the whole value of the declaration** — without
24
+ * it the two descriptions drift, and the first thing anybody notices is a field reading zeroes.
25
+ *
26
+ * ## A system's resources are resolved here, once, and `@driftengine/entities` never learns about them
27
+ *
28
+ * `system Walk { uses graph: NavGraph … }` asks to be handed a value this host owns. Without it a
29
+ * handle reaches a script only as a *function* parameter, and every per-entity walk has to be
30
+ * driven from TypeScript. The generated function takes a second parameter: a table keyed by **type
31
+ * name**, so a host supplies "the `NavGraph` this world has" and never learns what any script
32
+ * called its binding.
33
+ *
34
+ * The table is built at registration and captured, rather than assembled per tick. A resource does
35
+ * not change between ticks, and an object per system per tick is the allocation this package refuses
36
+ * everywhere else.
37
+ *
38
+ * **A missing resource is refused at registration, not left as `undefined`.** Passing nothing would
39
+ * reach a capability as an absent argument and produce a wrong *result* rather than an error, which
40
+ * is the trap `host.ts` step four is about. Init is where a person is watching.
41
+ *
42
+ * `SystemDefinition` gains nothing for this. A resource is a host's object, and the ECS has no
43
+ * business knowing one exists — so the table lives in the closure that already reaches through
44
+ * `module.exports`.
45
+ */
46
+ import { defineComponent, definePrefab, } from '@driftengine/entities';
47
+ /**
48
+ * Check every `from host` declaration against what the host registered.
49
+ *
50
+ * Throws on the first mismatch rather than collecting: a shape disagreement means the module was
51
+ * compiled against a different world than the one it is being loaded into, and the second error
52
+ * would be a consequence of the first.
53
+ */
54
+ export function assertHostShapes(module, registry) {
55
+ for (const declared of module.info.components ?? []) {
56
+ if (!declared.fromHost)
57
+ continue;
58
+ const registered = registry.get(declared.name);
59
+ if (registered === undefined) {
60
+ throw new Error(`\`${module.info.module}\` declares \`component ${declared.name} from host\`, and this host ` +
61
+ `registered no component of that name. It has ` +
62
+ `${[...registry.keys()].map((k) => `\`${k}\``).join(', ')}. A \`from host\` declaration ` +
63
+ 'asserts a shape rather than creating one, so there is nothing to fall back to.');
64
+ }
65
+ const actual = new Map(registered.schema.fields.map((field) => [field.name, field.type]));
66
+ for (const field of declared.schema.fields) {
67
+ const found = actual.get(field.name);
68
+ if (found === undefined) {
69
+ throw new Error(`\`${declared.name}.${field.name}\` is declared in \`${module.info.module}\` and the ` +
70
+ `registered component has no such field. It has ` +
71
+ `${[...actual.keys()].map((k) => `\`${k}\``).join(', ')}.`);
72
+ }
73
+ if (found !== field.type) {
74
+ throw new Error(`\`${declared.name}.${field.name}\` is \`${field.type}\` in \`${module.info.module}\` and ` +
75
+ `\`${found}\` in the component this host registered. A script reading it would get a ` +
76
+ 'value of a type it was not compiled against.');
77
+ }
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * Build every component, prefab and system a module declares.
83
+ *
84
+ * Components are registered into `registry` as they are made, so a prefab or a system later in the
85
+ * same module finds them — and so a second module loaded against the same registry shares them
86
+ * rather than making a second store for one name.
87
+ */
88
+ export function registerEntityModule(module, registry, resources = new Map()) {
89
+ assertHostShapes(module, registry);
90
+ const components = [];
91
+ for (const declared of module.info.components ?? []) {
92
+ /* A host-declared component is already registered; asserting its shape is all this does. */
93
+ if (declared.fromHost)
94
+ continue;
95
+ const existing = registry.get(declared.name);
96
+ /* A module reloaded keeps the store it already made: replacing the type would orphan every
97
+ entity holding one, which is a world that silently loses its components on an edit. */
98
+ const type = existing ?? defineComponent(declared.schema);
99
+ registry.set(declared.name, type);
100
+ components.push(type);
101
+ }
102
+ const prefabs = (module.info.prefabs ?? []).map((declared) => definePrefab(declared.name, declared.components.map((component) => {
103
+ const type = registry.get(component.name);
104
+ if (type === undefined) {
105
+ throw new Error(`prefab \`${declared.name}\` names the component \`${component.name}\`, which nothing ` +
106
+ 'has registered. The compiler refuses this, so a module reaching here was built ' +
107
+ 'against a different set of declarations than it is being loaded with.');
108
+ }
109
+ return [type, component.values];
110
+ })));
111
+ const systems = (module.info.systems ?? []).map((declared) => {
112
+ const resolve = (names) => names.map((name) => {
113
+ const type = registry.get(name);
114
+ if (type === undefined) {
115
+ throw new Error(`system \`${declared.name}\` declares \`${name}\`, which nothing has registered.`);
116
+ }
117
+ return type;
118
+ });
119
+ /*
120
+ * Built once and frozen, so a system cannot acquire a resource by writing into the table it was
121
+ * handed — which would be state shared between ticks that nothing declares.
122
+ */
123
+ const lent = {};
124
+ for (const resource of declared.uses ?? []) {
125
+ if (!resources.has(resource.type)) {
126
+ throw new Error(`system \`${declared.name}\` uses \`${resource.name}: ${resource.type}\`, and no ` +
127
+ `\`${resource.type}\` was supplied. Pass one in the third argument to ` +
128
+ '`registerEntityModule`, keyed by the type name. Refused here rather than passed as ' +
129
+ 'nothing, because an absent argument reaches a capability as a wrong answer.');
130
+ }
131
+ lent[resource.type] = resources.get(resource.type);
132
+ }
133
+ Object.freeze(lent);
134
+ return {
135
+ name: declared.name,
136
+ reads: resolve(declared.reads),
137
+ writes: resolve(declared.writes),
138
+ after: declared.after,
139
+ everyTicks: declared.everyTicks,
140
+ /*
141
+ * Through `exports` on every tick, never captured here. See the header: a captured function
142
+ * is pinned in the schedule across a hot patch, and the symptom is an edit that reports
143
+ * success and changes nothing.
144
+ */
145
+ run(view) {
146
+ const fn = module.exports[declared.name];
147
+ if (typeof fn !== 'function') {
148
+ throw new Error(`\`${declared.name}\` is not exported by \`${module.info.module}\` any more. A disposed ` +
149
+ 'module left in a schedule fails here rather than silently doing nothing.');
150
+ }
151
+ /* The second argument is passed whether or not this system asked for anything. A generated
152
+ function without `uses` clauses declares one parameter and ignores it, which is what makes
153
+ the quiet form quiet in the compiler and costs nothing here. */
154
+ fn(view, lent);
155
+ },
156
+ };
157
+ });
158
+ return { components, prefabs, systems };
159
+ }
package/dist/host.d.ts ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The engine as a DriftScript host: a registry describing what it provides, and the map that binds
3
+ * a compiled module to the real implementations.
4
+ *
5
+ * **This package is the only place the language and the engine are coupled**, and this file is the
6
+ * only place in it that knows about both at once. `driftscript` imports no `@driftengine/*` at all,
7
+ * asserted three ways; everything the engine contributes arrives through here.
8
+ *
9
+ * ---
10
+ *
11
+ * ## Adding a binding when a track lands
12
+ *
13
+ * **A capability that exists and is not bound is a capability DriftScript cannot reach**, and
14
+ * nothing fails when one is missed — a script author simply finds the module refused and assumes
15
+ * the track has not shipped. That silence is why this note exists rather than a checklist somewhere
16
+ * else.
17
+ *
18
+ * When a track lands, or a package grows a public function a script would want:
19
+ *
20
+ * 1. **Describe it.** A `defineCapability` entry in `bindings/`: its parameters and return as type
21
+ * names, its effects, whether a `@deterministic` function may call it, and a sentence of
22
+ * documentation that hover will show.
23
+ * 2. **Implement it.** One entry in the module's implementation map. A binding is a *lookup*; if it
24
+ * needs the engine to grow a function, that function belongs in the package that owns the
25
+ * subsystem.
26
+ * 3. **Provide it.** Add the module to `ENGINE_MODULES`, or a target cannot link it.
27
+ * 4. **Check the trap.** If the TypeScript signature has an optional parameter whose absence
28
+ * produces a *wrong result* rather than an error, make it required here and say why — a script
29
+ * author is further from the subsystem than a TypeScript caller and meets it first.
30
+ * `bindings/animation.ts` is the worked example.
31
+ * 5. **Correct the table.** `docs/CAPABILITIES.md` names which modules DriftScript reaches, and
32
+ * `driftscript`'s own linker names which do not, and refuses them by name.
33
+ * Those two lists must agree; a test asserts it.
34
+ *
35
+ * A binding is cheap — a table entry and a lookup — and is the difference between a subsystem
36
+ * a consumer can script and one they can only call from TypeScript.
37
+ */
38
+ import type { AudioGraph, SoundRegistry } from '@driftengine/audio';
39
+ import { type BehaviorServices } from './bindings/behavior.ts';
40
+ import { type NavigationServices } from './bindings/navigation.ts';
41
+ import { type CapabilityRegistry, type DriftModule, type TargetManifest } from 'driftscript';
42
+ import { type AiServices } from './bindings/ai.ts';
43
+ import { type XrRuntime } from './bindings/xr.ts';
44
+ import type { CoreServices } from './bindings/core.ts';
45
+ import { type EntityServices } from './bindings/entities.ts';
46
+ import { type ChemistryServices } from './bindings/chemistry.ts';
47
+ /**
48
+ * What the engine can provide. Everything wired; nothing that is not.
49
+ *
50
+ * `drift/core` is deliberately absent: §11 gives its provider as `startLoop` and `LoopHooks`, which
51
+ * are what *drives* a script rather than what a script calls. A module with nothing to call is not
52
+ * registered rather than registered empty.
53
+ */
54
+ export declare const ENGINE_MODULES: readonly string[];
55
+ /**
56
+ * A registry describing this engine's capabilities.
57
+ *
58
+ * Built rather than exported as a constant, because a registry refuses a duplicate registration —
59
+ * two consumers sharing one module-level instance would be one consumer's startup failing on
60
+ * another's import. Cheap: a few dozen object literals.
61
+ */
62
+ export declare function engineRegistry(): CapabilityRegistry;
63
+ /**
64
+ * A target providing everything this engine has wired.
65
+ *
66
+ * A consumer that wants less passes its own list. **A consumer that wants more cannot** — the
67
+ * manifest may only name modules something provides, so a target claiming `drift/ecs` is refused
68
+ * at the manifest rather than at every call site.
69
+ */
70
+ export declare function engineTarget(name?: string): TargetManifest;
71
+ export interface HostServices extends CoreServices {
72
+ /**
73
+ * The agents a script may name, and what the two bridges answer for them.
74
+ *
75
+ * `agents` alone is a supported configuration: a host with no navigation graph and no session
76
+ * still gets `wake`, `consider`, `intentId` and `degraded`. The bridge sub-services are what
77
+ * `reachable`, `navigate`, `path` and `deciding` need, and a capability whose service is absent
78
+ * fails at the call saying which one to provide rather than answering falsely.
79
+ */
80
+ readonly ai?: AiServices;
81
+ /**
82
+ * The live session, if a host has one.
83
+ *
84
+ * Absent means `drift/xr` calls fail at the call naming the namespace, which is the bargain every
85
+ * optional service here makes. A stub would be worse than usual: a script asking where a head is
86
+ * and getting the origin cannot tell a headset at the world origin from no headset at all, and
87
+ * one of those is a scene that draws and the other is a scene nobody is in.
88
+ */
89
+ readonly xr?: XrRuntime;
90
+ readonly audio?: {
91
+ readonly graph: AudioGraph;
92
+ readonly registry: SoundRegistry;
93
+ };
94
+ /**
95
+ * The component types, and any prefabs, a script may name.
96
+ *
97
+ * Absent means a script's `drift/ecs` calls fail at the call with the namespace's own name in the
98
+ * error — the same bargain every other optional service makes, and the reason none of them is
99
+ * stubbed.
100
+ */
101
+ readonly entities?: EntityServices;
102
+ /**
103
+ * The chemistry world a script reads and writes.
104
+ *
105
+ * Absent means `drift/chemistry` calls fail at the call naming the namespace — the same bargain
106
+ * every other optional service makes, and the reason none of them is stubbed. A stub would make a
107
+ * script whose fire never lights indistinguishable from one whose chemistry is not connected.
108
+ */
109
+ readonly chemistry?: ChemistryServices;
110
+ /**
111
+ * The network scripts path over: a graph, and one search reused across every agent.
112
+ *
113
+ * Absent means `drift/navigation` calls fail at the call naming the namespace, like every other
114
+ * optional service. A stub would be worse here than elsewhere: an agent given a route of no nodes
115
+ * behaves exactly like one that arrived, so a world whose navigation was never connected would
116
+ * look like a world where every agent is already where it wants to be.
117
+ */
118
+ readonly navigation?: NavigationServices;
119
+ /**
120
+ * What a behaviour tick is handed.
121
+ *
122
+ * Absent means `drift/behavior` calls fail at the call naming the namespace. A stub is
123
+ * particularly bad here for the reason `doing` exists: an agent whose routine never runs looks
124
+ * exactly like one that is standing about because it has nothing to do.
125
+ */
126
+ readonly behavior?: BehaviorServices;
127
+ }
128
+ /**
129
+ * The implementation map a generated module's `__bind` receives.
130
+ *
131
+ * Keyed by logical module name, because that is what generated code looks up: `audio` in a `.drs`
132
+ * file is `$host['drift/audio']` in the output.
133
+ *
134
+ * **A service the consumer did not supply is absent rather than stubbed.** A module that binds it
135
+ * and then calls it fails at the call with the namespace's own name in the error, which is what the
136
+ * rule against silent no-ops asks for — a stub that quietly did nothing would make a script that
137
+ * plays no sound indistinguishable from one whose audio is not connected.
138
+ */
139
+ export declare function engineImplementations(services: HostServices): Record<string, unknown>;
140
+ export type BindResult = {
141
+ readonly bound: true;
142
+ } | {
143
+ readonly bound: false;
144
+ readonly reason: string;
145
+ };
146
+ /**
147
+ * Give a loaded module its host, and refuse in words when the host cannot satisfy it.
148
+ *
149
+ * Checked against `__drift.requires` before anything is called, so a missing service is reported at
150
+ * bind time naming the module — rather than at the first capability call, deep in a frame, as an
151
+ * undefined property.
152
+ */
153
+ export declare function bindModule(module: DriftModule, services: HostServices): BindResult;