@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.
- package/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +81 -0
- package/dist/bindings/ai.d.ts +66 -0
- package/dist/bindings/ai.js +173 -0
- package/dist/bindings/animation.d.ts +13 -0
- package/dist/bindings/animation.js +177 -0
- package/dist/bindings/audio.d.ts +56 -0
- package/dist/bindings/audio.js +126 -0
- package/dist/bindings/behavior.d.ts +36 -0
- package/dist/bindings/behavior.js +101 -0
- package/dist/bindings/chemistry.d.ts +72 -0
- package/dist/bindings/chemistry.js +253 -0
- package/dist/bindings/core.d.ts +172 -0
- package/dist/bindings/core.js +814 -0
- package/dist/bindings/editor.d.ts +53 -0
- package/dist/bindings/editor.js +248 -0
- package/dist/bindings/entities.d.ts +73 -0
- package/dist/bindings/entities.js +339 -0
- package/dist/bindings/interface.d.ts +55 -0
- package/dist/bindings/interface.js +200 -0
- package/dist/bindings/navigation.d.ts +36 -0
- package/dist/bindings/navigation.js +255 -0
- package/dist/bindings/network.d.ts +51 -0
- package/dist/bindings/network.js +96 -0
- package/dist/bindings/render.d.ts +71 -0
- package/dist/bindings/render.js +143 -0
- package/dist/bindings/rollback.d.ts +41 -0
- package/dist/bindings/rollback.js +74 -0
- package/dist/bindings/sprites.d.ts +53 -0
- package/dist/bindings/sprites.js +186 -0
- package/dist/bindings/terrain.d.ts +54 -0
- package/dist/bindings/terrain.js +138 -0
- package/dist/bindings/xr.d.ts +56 -0
- package/dist/bindings/xr.js +155 -0
- package/dist/entityHost.d.ts +80 -0
- package/dist/entityHost.js +159 -0
- package/dist/host.d.ts +153 -0
- package/dist/host.js +233 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +18 -0
- package/package.json +68 -0
- package/src/bindings/ai.ts +306 -0
- package/src/bindings/animation.ts +346 -0
- package/src/bindings/audio.ts +185 -0
- package/src/bindings/behavior.ts +181 -0
- package/src/bindings/chemistry.ts +548 -0
- package/src/bindings/core.ts +1604 -0
- package/src/bindings/editor.ts +393 -0
- package/src/bindings/entities.ts +550 -0
- package/src/bindings/interface.ts +300 -0
- package/src/bindings/navigation.ts +367 -0
- package/src/bindings/network.ts +178 -0
- package/src/bindings/render.ts +191 -0
- package/src/bindings/rollback.ts +110 -0
- package/src/bindings/sprites.ts +310 -0
- package/src/bindings/terrain.ts +180 -0
- package/src/bindings/xr.ts +272 -0
- package/src/entityHost.ts +219 -0
- package/src/host.ts +465 -0
- package/src/index.ts +41 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CapabilityDefinition, OpaqueType } from 'driftscript';
|
|
2
|
+
export declare const ROLLBACK_MODULE = "drift/rollback";
|
|
3
|
+
/**
|
|
4
|
+
* `drift/rollback` — what a script can ask about the rewind it is running inside.
|
|
5
|
+
*
|
|
6
|
+
* **Every capability here is `nondeterministic`, and that is the whole design.** The obvious reading
|
|
7
|
+
* is that these are reads and reads are cheap, so they should be as permissive as possible. The
|
|
8
|
+
* opposite is true, and the reason is the thing being read: *whether this tick is a replay*, *how
|
|
9
|
+
* far back the loop can still reach*, *which ticks are settled*. All of them move with packet
|
|
10
|
+
* timing, and a `@deterministic` system that branched on one would take a different path live than
|
|
11
|
+
* on replay — producing a world that differs from the world it is replaying, for a reason that
|
|
12
|
+
* looks nothing like a networking bug.
|
|
13
|
+
*
|
|
14
|
+
* That is the same call `bindings/ui.ts` made for `hovered` and `pressed`, and `bindings/network.ts`
|
|
15
|
+
* makes for the confirmed watermark. Stated once more here because this is the module where getting
|
|
16
|
+
* it wrong would be most tempting and least visible.
|
|
17
|
+
*
|
|
18
|
+
* ## `isReplaying` is the one worth being explicit about
|
|
19
|
+
*
|
|
20
|
+
* A script asking "am I being replayed" is almost always about to do something that should not
|
|
21
|
+
* happen twice — play a sound, spawn a particle, send a message. **That is a legitimate use and it
|
|
22
|
+
* is precisely why the effect is what it is**: the function doing it is not a simulation function,
|
|
23
|
+
* it is a presentation function that happens to run beside one, and the annotation should say so.
|
|
24
|
+
*
|
|
25
|
+
* A script using it to change *simulation* state is writing a bug, and nothing here can stop that.
|
|
26
|
+
* What it can do is make the deterministic annotation refuse to compile over it, which is the
|
|
27
|
+
* difference between a bug that is found by the checker and one that is found by two players
|
|
28
|
+
* disagreeing about who won.
|
|
29
|
+
*
|
|
30
|
+
* ## There is no `rollback.read` effect and this does not add one
|
|
31
|
+
*
|
|
32
|
+
* The language named `behavior.read` and `behavior.write` ahead of their provider precisely so a
|
|
33
|
+
* track would not need a release first, and it named `network.read` and `network.write` the same
|
|
34
|
+
* way — but nothing for rollback. `nondeterministic` is honest for every capability here, so no
|
|
35
|
+
* release is needed. *Reversed by* a consumer who wants a `@deterministic` function to read
|
|
36
|
+
* something in this module, at which point the question is which capability and why, and the answer
|
|
37
|
+
* is a one-line change to the `Effect` union.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ROLLBACK_TYPES: readonly OpaqueType[];
|
|
40
|
+
export declare const ROLLBACK_CAPABILITIES: readonly CapabilityDefinition[];
|
|
41
|
+
export declare function rollbackImplementation(): Record<string, unknown>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { defineCapability } from 'driftscript';
|
|
2
|
+
export const ROLLBACK_MODULE = 'drift/rollback';
|
|
3
|
+
/**
|
|
4
|
+
* `drift/rollback` — what a script can ask about the rewind it is running inside.
|
|
5
|
+
*
|
|
6
|
+
* **Every capability here is `nondeterministic`, and that is the whole design.** The obvious reading
|
|
7
|
+
* is that these are reads and reads are cheap, so they should be as permissive as possible. The
|
|
8
|
+
* opposite is true, and the reason is the thing being read: *whether this tick is a replay*, *how
|
|
9
|
+
* far back the loop can still reach*, *which ticks are settled*. All of them move with packet
|
|
10
|
+
* timing, and a `@deterministic` system that branched on one would take a different path live than
|
|
11
|
+
* on replay — producing a world that differs from the world it is replaying, for a reason that
|
|
12
|
+
* looks nothing like a networking bug.
|
|
13
|
+
*
|
|
14
|
+
* That is the same call `bindings/ui.ts` made for `hovered` and `pressed`, and `bindings/network.ts`
|
|
15
|
+
* makes for the confirmed watermark. Stated once more here because this is the module where getting
|
|
16
|
+
* it wrong would be most tempting and least visible.
|
|
17
|
+
*
|
|
18
|
+
* ## `isReplaying` is the one worth being explicit about
|
|
19
|
+
*
|
|
20
|
+
* A script asking "am I being replayed" is almost always about to do something that should not
|
|
21
|
+
* happen twice — play a sound, spawn a particle, send a message. **That is a legitimate use and it
|
|
22
|
+
* is precisely why the effect is what it is**: the function doing it is not a simulation function,
|
|
23
|
+
* it is a presentation function that happens to run beside one, and the annotation should say so.
|
|
24
|
+
*
|
|
25
|
+
* A script using it to change *simulation* state is writing a bug, and nothing here can stop that.
|
|
26
|
+
* What it can do is make the deterministic annotation refuse to compile over it, which is the
|
|
27
|
+
* difference between a bug that is found by the checker and one that is found by two players
|
|
28
|
+
* disagreeing about who won.
|
|
29
|
+
*
|
|
30
|
+
* ## There is no `rollback.read` effect and this does not add one
|
|
31
|
+
*
|
|
32
|
+
* The language named `behavior.read` and `behavior.write` ahead of their provider precisely so a
|
|
33
|
+
* track would not need a release first, and it named `network.read` and `network.write` the same
|
|
34
|
+
* way — but nothing for rollback. `nondeterministic` is honest for every capability here, so no
|
|
35
|
+
* release is needed. *Reversed by* a consumer who wants a `@deterministic` function to read
|
|
36
|
+
* something in this module, at which point the question is which capability and why, and the answer
|
|
37
|
+
* is a one-line change to the `Effect` union.
|
|
38
|
+
*/
|
|
39
|
+
export const ROLLBACK_TYPES = [
|
|
40
|
+
{
|
|
41
|
+
module: ROLLBACK_MODULE,
|
|
42
|
+
name: 'Rewind',
|
|
43
|
+
doc: 'The rewind loop: which tick is running, whether this is a replay, and how far back it can reach.',
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
const define = (name, returns, doc) => defineCapability({
|
|
47
|
+
module: ROLLBACK_MODULE,
|
|
48
|
+
name,
|
|
49
|
+
signature: `fn(rewind: Rewind) -> ${returns}`,
|
|
50
|
+
params: [{ name: 'rewind', type: 'Rewind' }],
|
|
51
|
+
returns,
|
|
52
|
+
effects: ['nondeterministic'],
|
|
53
|
+
deterministic: false,
|
|
54
|
+
doc,
|
|
55
|
+
implementation: `${ROLLBACK_MODULE}.${name}`,
|
|
56
|
+
});
|
|
57
|
+
export const ROLLBACK_CAPABILITIES = [
|
|
58
|
+
define('tick', 'i32', 'The last tick that was stepped, or -1 before the first.'),
|
|
59
|
+
define('isReplaying', 'bool', 'Whether this tick is being re-run to correct a mispredicted one. Anything that should not happen twice — a sound, a particle, a message — asks this first.'),
|
|
60
|
+
define('depth', 'i32', 'How many ticks back a rewind can reach. Eight at 60 Hz is 133 milliseconds of correction.'),
|
|
61
|
+
define('earliest', 'i32', 'The oldest tick still retained, or -1. An input older than this cannot be applied and the session cannot be made correct.'),
|
|
62
|
+
define('replays', 'i32', 'How many rewinds have happened. A session correcting constantly has a link problem.'),
|
|
63
|
+
define('replayedTicks', 'i32', 'How many ticks those rewinds re-ran in total. Divided by `replays`, the average depth of a correction.'),
|
|
64
|
+
];
|
|
65
|
+
export function rollbackImplementation() {
|
|
66
|
+
return {
|
|
67
|
+
tick: (rewind) => rewind.tick,
|
|
68
|
+
isReplaying: (rewind) => rewind.isReplaying,
|
|
69
|
+
depth: (rewind) => rewind.depth,
|
|
70
|
+
earliest: (rewind) => rewind.earliest,
|
|
71
|
+
replays: (rewind) => rewind.stats.replays,
|
|
72
|
+
replayedTicks: (rewind) => rewind.stats.replayedTicks,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { CapabilityDefinition, OpaqueType } from 'driftscript';
|
|
2
|
+
export declare const SPRITES_MODULE = "drift/2d";
|
|
3
|
+
/**
|
|
4
|
+
* `drift/2d` — quads with a picture on them, and the grids of them.
|
|
5
|
+
*
|
|
6
|
+
* ```drs
|
|
7
|
+
* import { sprite, tilemap } from "drift/2d" as sprites
|
|
8
|
+
*
|
|
9
|
+
* fn hud(batch: SpriteBatch) {
|
|
10
|
+
* sprites.sprite(batch, 0, 10, 10, 32, 32)
|
|
11
|
+
* }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* **The `as` is not decoration and this module is why the language has it.** A capability is reached
|
|
15
|
+
* through a namespace and the namespace was the last segment of the path, so a call here would have
|
|
16
|
+
* been `2d.sprite(...)` — a number followed by an identifier, refused by the lexer before the
|
|
17
|
+
* checker saw it. This binding was written on 2026-09-03, wired, found uncallable, and **withdrawn
|
|
18
|
+
* rather than shipped**, because a capability nobody can spell is the silent no-op this repository
|
|
19
|
+
* forbids wearing a feature's clothes. DriftScript 1.11.0 answered it at the import, and the
|
|
20
|
+
* language's own changelog carries the five spellings that were tried and failed.
|
|
21
|
+
*
|
|
22
|
+
* So a script naming this module must name the namespace too, and `DS0139` says so at the import
|
|
23
|
+
* with the line to write rather than leaving an author at `DS0003` inside their own call.
|
|
24
|
+
*
|
|
25
|
+
* ## The effects, and why this one needed nothing from the language
|
|
26
|
+
*
|
|
27
|
+
* Unlike `drift/terrain`, this module met no wall on effects. **A sprite is a thing that draws**,
|
|
28
|
+
* and `scene.write` is exactly what the language reserves for that — its own comment says a
|
|
29
|
+
* `SceneNode` "is what *draws*", and that writing one is a change to the view rather than to the
|
|
30
|
+
* simulation. Everything here that puts a quad in a batch is declared that way, and it is outside
|
|
31
|
+
* `DETERMINISTIC_EFFECTS` for the right reason: a `@deterministic` system decides where things
|
|
32
|
+
* *are*, and something else draws them.
|
|
33
|
+
*
|
|
34
|
+
* **Reading a sheet or a tile is `scene.read`**, which is inside the boundary, so a deterministic
|
|
35
|
+
* system may ask what frame a name is or what is in a cell. That asymmetry is the honest one:
|
|
36
|
+
* looking at the view is a read of state the simulation already produced.
|
|
37
|
+
*
|
|
38
|
+
* **`setTile` is a write to the view and is outside**, which is worth being explicit about because
|
|
39
|
+
* it reads as a limitation and is not one. A `Tilemap` here is *drawing* data — its cells are frame
|
|
40
|
+
* indices into a sheet, and `tilemap.ts` says so in its first line. A game's collision grid is the
|
|
41
|
+
* game's own array and is not this. A deterministic system that wants to dig a hole changes its own
|
|
42
|
+
* grid and lets a drawing system follow.
|
|
43
|
+
*/
|
|
44
|
+
export declare const SPRITES_TYPES: readonly OpaqueType[];
|
|
45
|
+
export declare const SPRITES_CAPABILITIES: readonly CapabilityDefinition[];
|
|
46
|
+
/**
|
|
47
|
+
* **No services, and that is the shape rather than an omission.**
|
|
48
|
+
*
|
|
49
|
+
* Every capability takes the batch, sheet or map it acts on, so the module needs nothing from the
|
|
50
|
+
* host to answer — they reach a script through `uses`, the way a `Terrain` and a `NavGraph` do. So
|
|
51
|
+
* this is registered unconditionally, and a script that never receives a batch never calls it.
|
|
52
|
+
*/
|
|
53
|
+
export declare function spritesImplementation(): Record<string, unknown>;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createSpriteFrame, drawSprite, drawTilemap, frameOf, setTile, sheetFrame, tileAt, } from '@driftengine/ui2d';
|
|
2
|
+
import { defineCapability } from 'driftscript';
|
|
3
|
+
export const SPRITES_MODULE = 'drift/2d';
|
|
4
|
+
/**
|
|
5
|
+
* `drift/2d` — quads with a picture on them, and the grids of them.
|
|
6
|
+
*
|
|
7
|
+
* ```drs
|
|
8
|
+
* import { sprite, tilemap } from "drift/2d" as sprites
|
|
9
|
+
*
|
|
10
|
+
* fn hud(batch: SpriteBatch) {
|
|
11
|
+
* sprites.sprite(batch, 0, 10, 10, 32, 32)
|
|
12
|
+
* }
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* **The `as` is not decoration and this module is why the language has it.** A capability is reached
|
|
16
|
+
* through a namespace and the namespace was the last segment of the path, so a call here would have
|
|
17
|
+
* been `2d.sprite(...)` — a number followed by an identifier, refused by the lexer before the
|
|
18
|
+
* checker saw it. This binding was written on 2026-09-03, wired, found uncallable, and **withdrawn
|
|
19
|
+
* rather than shipped**, because a capability nobody can spell is the silent no-op this repository
|
|
20
|
+
* forbids wearing a feature's clothes. DriftScript 1.11.0 answered it at the import, and the
|
|
21
|
+
* language's own changelog carries the five spellings that were tried and failed.
|
|
22
|
+
*
|
|
23
|
+
* So a script naming this module must name the namespace too, and `DS0139` says so at the import
|
|
24
|
+
* with the line to write rather than leaving an author at `DS0003` inside their own call.
|
|
25
|
+
*
|
|
26
|
+
* ## The effects, and why this one needed nothing from the language
|
|
27
|
+
*
|
|
28
|
+
* Unlike `drift/terrain`, this module met no wall on effects. **A sprite is a thing that draws**,
|
|
29
|
+
* and `scene.write` is exactly what the language reserves for that — its own comment says a
|
|
30
|
+
* `SceneNode` "is what *draws*", and that writing one is a change to the view rather than to the
|
|
31
|
+
* simulation. Everything here that puts a quad in a batch is declared that way, and it is outside
|
|
32
|
+
* `DETERMINISTIC_EFFECTS` for the right reason: a `@deterministic` system decides where things
|
|
33
|
+
* *are*, and something else draws them.
|
|
34
|
+
*
|
|
35
|
+
* **Reading a sheet or a tile is `scene.read`**, which is inside the boundary, so a deterministic
|
|
36
|
+
* system may ask what frame a name is or what is in a cell. That asymmetry is the honest one:
|
|
37
|
+
* looking at the view is a read of state the simulation already produced.
|
|
38
|
+
*
|
|
39
|
+
* **`setTile` is a write to the view and is outside**, which is worth being explicit about because
|
|
40
|
+
* it reads as a limitation and is not one. A `Tilemap` here is *drawing* data — its cells are frame
|
|
41
|
+
* indices into a sheet, and `tilemap.ts` says so in its first line. A game's collision grid is the
|
|
42
|
+
* game's own array and is not this. A deterministic system that wants to dig a hole changes its own
|
|
43
|
+
* grid and lets a drawing system follow.
|
|
44
|
+
*/
|
|
45
|
+
export const SPRITES_TYPES = [
|
|
46
|
+
{
|
|
47
|
+
module: SPRITES_MODULE,
|
|
48
|
+
name: 'SpriteBatch',
|
|
49
|
+
doc: 'One frame of quads, in the order they were put in. Reset it, fill it, draw it.',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
module: SPRITES_MODULE,
|
|
53
|
+
name: 'SpriteSheet',
|
|
54
|
+
doc: 'The rectangles of one texture that each hold a picture, addressed by index or by name.',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
module: SPRITES_MODULE,
|
|
58
|
+
name: 'Tilemap',
|
|
59
|
+
doc: 'A grid of sheet frames. Drawing one costs the view rather than the map.',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
const define = (name, params, returns, effects, doc) => defineCapability({
|
|
63
|
+
module: SPRITES_MODULE,
|
|
64
|
+
name,
|
|
65
|
+
signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> ${returns}`,
|
|
66
|
+
params: [...params],
|
|
67
|
+
returns,
|
|
68
|
+
effects: [...effects],
|
|
69
|
+
/*
|
|
70
|
+
* Every float here is `f32` and none is `float`. `float` is width-polymorphic and takes its
|
|
71
|
+
* width from another `float`, so a capability returning one must take one — a rule Track L paid
|
|
72
|
+
* for. Nothing here returns a float at all, and a batch stores its instances as `Float32Array`,
|
|
73
|
+
* so `f32` is what the number actually is rather than a width chosen to satisfy a checker.
|
|
74
|
+
*/
|
|
75
|
+
deterministic: !effects.includes('scene.write'),
|
|
76
|
+
doc,
|
|
77
|
+
implementation: `${SPRITES_MODULE}.${name}`,
|
|
78
|
+
});
|
|
79
|
+
const AT = [
|
|
80
|
+
{ name: 'x', type: 'f32' },
|
|
81
|
+
{ name: 'y', type: 'f32' },
|
|
82
|
+
{ name: 'w', type: 'f32' },
|
|
83
|
+
{ name: 'h', type: 'f32' },
|
|
84
|
+
];
|
|
85
|
+
export const SPRITES_CAPABILITIES = [
|
|
86
|
+
define('sprite', [{ name: 'batch', type: 'SpriteBatch' }, { name: 'texture', type: 'i32' }, ...AT], 'void', ['scene.write'], 'Put one quad in the batch, covering the whole of a texture.'),
|
|
87
|
+
define('tinted', [
|
|
88
|
+
{ name: 'batch', type: 'SpriteBatch' },
|
|
89
|
+
{ name: 'texture', type: 'i32' },
|
|
90
|
+
...AT,
|
|
91
|
+
{ name: 'r', type: 'f32' },
|
|
92
|
+
{ name: 'g', type: 'f32' },
|
|
93
|
+
{ name: 'b', type: 'f32' },
|
|
94
|
+
{ name: 'a', type: 'f32' },
|
|
95
|
+
], 'void', ['scene.write'], 'The same, multiplied by a colour. White is the identity, so a white texture draws exactly this colour — which is how a solid rectangle is drawn.'),
|
|
96
|
+
define('frame', [
|
|
97
|
+
{ name: 'batch', type: 'SpriteBatch' },
|
|
98
|
+
{ name: 'sheet', type: 'SpriteSheet' },
|
|
99
|
+
{ name: 'frame', type: 'i32' },
|
|
100
|
+
...AT,
|
|
101
|
+
], 'void', ['scene.write'], 'Put one frame of a sheet in the batch. A frame the sheet does not have draws the whole sheet, which is unmistakable.'),
|
|
102
|
+
define('named', [
|
|
103
|
+
{ name: 'sheet', type: 'SpriteSheet' },
|
|
104
|
+
{ name: 'name', type: 'String' },
|
|
105
|
+
], 'i32', ['scene.read'], 'The index a name has in a sheet, or -1. Ask once and keep the number: a name lookup is a hash and a frame is an array read.'),
|
|
106
|
+
define('frames', [{ name: 'sheet', type: 'SpriteSheet' }], 'i32', ['scene.read'], 'How many frames the sheet was cut into.'),
|
|
107
|
+
define('tilemap', [
|
|
108
|
+
{ name: 'batch', type: 'SpriteBatch' },
|
|
109
|
+
{ name: 'map', type: 'Tilemap' },
|
|
110
|
+
{ name: 'sheet', type: 'SpriteSheet' },
|
|
111
|
+
...AT,
|
|
112
|
+
], 'i32', ['scene.write'], 'Draw the tiles a view rectangle can see, and answer how many that was. The cost is the view rather than the map.'),
|
|
113
|
+
define('tile', [
|
|
114
|
+
{ name: 'map', type: 'Tilemap' },
|
|
115
|
+
{ name: 'column', type: 'i32' },
|
|
116
|
+
{ name: 'row', type: 'i32' },
|
|
117
|
+
], 'i32', ['scene.read'], 'The frame in a cell, or -1 for an empty one — including for a cell outside the map, which answers empty rather than another row.'),
|
|
118
|
+
define('setTile', [
|
|
119
|
+
{ name: 'map', type: 'Tilemap' },
|
|
120
|
+
{ name: 'column', type: 'i32' },
|
|
121
|
+
{ name: 'row', type: 'i32' },
|
|
122
|
+
{ name: 'tile', type: 'i32' },
|
|
123
|
+
], 'void', ['scene.write'], 'Put a frame in a cell. A cell outside the map is ignored rather than wrapping into another row.'),
|
|
124
|
+
define('columns', [{ name: 'map', type: 'Tilemap' }], 'i32', ['scene.read'], 'How wide the map is, in cells.'),
|
|
125
|
+
define('rows', [{ name: 'map', type: 'Tilemap' }], 'i32', ['scene.read'], 'How tall the map is, in cells.'),
|
|
126
|
+
define('count', [{ name: 'batch', type: 'SpriteBatch' }], 'i32', ['scene.read'], 'How many quads are in the batch this frame.'),
|
|
127
|
+
/*
|
|
128
|
+
* **A script can see the batch overflow, which is the whole reason this is exposed.** The batch
|
|
129
|
+
* does not grow: past its capacity a draw is counted and dropped, and a caller with no way to
|
|
130
|
+
* read that count finds out by noticing something missing from a corner of the screen.
|
|
131
|
+
*/
|
|
132
|
+
define('dropped', [{ name: 'batch', type: 'SpriteBatch' }], 'i32', ['scene.read'], 'How many quads did not fit this frame. Zero is the only good value; anything else is a batch that wants a bigger capacity.'),
|
|
133
|
+
];
|
|
134
|
+
/**
|
|
135
|
+
* **No services, and that is the shape rather than an omission.**
|
|
136
|
+
*
|
|
137
|
+
* Every capability takes the batch, sheet or map it acts on, so the module needs nothing from the
|
|
138
|
+
* host to answer — they reach a script through `uses`, the way a `Terrain` and a `NavGraph` do. So
|
|
139
|
+
* this is registered unconditionally, and a script that never receives a batch never calls it.
|
|
140
|
+
*/
|
|
141
|
+
export function spritesImplementation() {
|
|
142
|
+
/* One frame rectangle for every `frame` call. These run per draw and may not allocate. */
|
|
143
|
+
const scratch = createSpriteFrame();
|
|
144
|
+
const place = { x: 0, y: 0, w: 0, h: 0 };
|
|
145
|
+
const tint = new Float32Array(4);
|
|
146
|
+
const view = { x: 0, y: 0, w: 0, h: 0 };
|
|
147
|
+
const at = (x, y, w, h) => {
|
|
148
|
+
place.x = x;
|
|
149
|
+
place.y = y;
|
|
150
|
+
place.w = w;
|
|
151
|
+
place.h = h;
|
|
152
|
+
return place;
|
|
153
|
+
};
|
|
154
|
+
return {
|
|
155
|
+
sprite: (batch, texture, x, y, w, h) => {
|
|
156
|
+
drawSprite(batch, texture, at(x, y, w, h), null, null);
|
|
157
|
+
},
|
|
158
|
+
tinted: (batch, texture, x, y, w, h, r, g, b, a) => {
|
|
159
|
+
tint[0] = r;
|
|
160
|
+
tint[1] = g;
|
|
161
|
+
tint[2] = b;
|
|
162
|
+
tint[3] = a;
|
|
163
|
+
drawSprite(batch, texture, at(x, y, w, h), null, tint);
|
|
164
|
+
},
|
|
165
|
+
frame: (batch, sheet, frame, x, y, w, h) => {
|
|
166
|
+
drawSprite(batch, sheet.texture, at(x, y, w, h), sheetFrame(sheet, frame, scratch), null);
|
|
167
|
+
},
|
|
168
|
+
named: (sheet, name) => frameOf(sheet, name),
|
|
169
|
+
frames: (sheet) => sheet.count,
|
|
170
|
+
tilemap: (batch, map, sheet, x, y, w, h) => {
|
|
171
|
+
view.x = x;
|
|
172
|
+
view.y = y;
|
|
173
|
+
view.w = w;
|
|
174
|
+
view.h = h;
|
|
175
|
+
return drawTilemap(batch, map, sheet, view, null);
|
|
176
|
+
},
|
|
177
|
+
tile: (map, column, row) => tileAt(map, column, row),
|
|
178
|
+
setTile: (map, column, row, tile) => {
|
|
179
|
+
setTile(map, column, row, tile);
|
|
180
|
+
},
|
|
181
|
+
columns: (map) => map.columns,
|
|
182
|
+
rows: (map) => map.rows,
|
|
183
|
+
count: (batch) => batch.count,
|
|
184
|
+
dropped: (batch) => batch.dropped,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CapabilityDefinition, OpaqueType } from 'driftscript';
|
|
2
|
+
export declare const TERRAIN_MODULE = "drift/terrain";
|
|
3
|
+
/**
|
|
4
|
+
* `drift/terrain` — where the ground is, and which way it faces.
|
|
5
|
+
*
|
|
6
|
+
* **The linker has refused this module by name since the language shipped**, saying it waits on
|
|
7
|
+
* Track L. It does not any more: `@driftengine/terrain` ships a heightfield whose query answers the
|
|
8
|
+
* surface that is *drawn*, which is the one property a script needs to be able to trust. A script
|
|
9
|
+
* placing a tree, deciding whether a slope is walkable, or dropping a dead body on the ground is
|
|
10
|
+
* asking this module and nothing else.
|
|
11
|
+
*
|
|
12
|
+
* **Every capability here is a read of static data**, so all of them are deterministic and a
|
|
13
|
+
* `@deterministic` system may ask where the ground is. That is the point — deciding where to stand
|
|
14
|
+
* belongs inside the fixed step.
|
|
15
|
+
*
|
|
16
|
+
* ## The effect these are declared under, and why it is not `terrain.read`
|
|
17
|
+
*
|
|
18
|
+
* **The language names no terrain effect.** `Effect` in `driftscript`'s registry runs from `pure`
|
|
19
|
+
* through `network.write`, and there is no `terrain.*` in it — `drift/terrain` is in
|
|
20
|
+
* `SPECIFIED_MODULES` and was missed when the effects were named. `drift/behavior` was in exactly
|
|
21
|
+
* this position and the language fixed it the other way round, naming `behavior.read` and
|
|
22
|
+
* `behavior.write` in 2026-08-28 *ahead of any provider*, with a comment saying it was so the track
|
|
23
|
+
* that built it would not need a language release first. Terrain did not get that.
|
|
24
|
+
*
|
|
25
|
+
* **This engine does not move the language's version**, which the navigation binding states where it
|
|
26
|
+
* meets the same wall. So the choice was between shipping under an effect that exists and not
|
|
27
|
+
* shipping.
|
|
28
|
+
*
|
|
29
|
+
* **`physics.read` is the one it ships under, and it is a description rather than a borrowing.**
|
|
30
|
+
* The terrain package's own tests assert that the surface a query answers, the surface that is
|
|
31
|
+
* drawn and the surface a body collides with are one surface — a ray cast against the mesh built
|
|
32
|
+
* from a patch lands where `heightAt` says the ground is. So "ask the terrain how high the ground
|
|
33
|
+
* is" and "cast a ray down and see what you hit" are the same question about the same surface, and
|
|
34
|
+
* the second is `physics.read` already. It is also in `DETERMINISTIC_EFFECTS`, which is what a
|
|
35
|
+
* script needs.
|
|
36
|
+
*
|
|
37
|
+
* **What would change it**: the language naming `terrain.read`. `docs/IMPROVEMENTS.md` carries that
|
|
38
|
+
* as a one-line change on the language's side and a one-word change here.
|
|
39
|
+
*/
|
|
40
|
+
export declare const TERRAIN_TYPES: readonly OpaqueType[];
|
|
41
|
+
export declare const TERRAIN_CAPABILITIES: readonly CapabilityDefinition[];
|
|
42
|
+
/**
|
|
43
|
+
* **No services, and that is the shape rather than an omission.**
|
|
44
|
+
*
|
|
45
|
+
* Every capability here takes the field it is asking about, so the module needs nothing from the
|
|
46
|
+
* host to answer — the terrain reaches a script the way a `NavGraph` does, as a resource handed in
|
|
47
|
+
* through `uses`. `drift/animation` made the same move on 2026-08-28 when `blendTree` was replaced
|
|
48
|
+
* by `blendSet` and `blendAt`: drive a thing the host built, rather than offering to build one out
|
|
49
|
+
* of values the language has no shape for.
|
|
50
|
+
*
|
|
51
|
+
* So this is registered unconditionally, and a script that never receives a `Terrain` simply never
|
|
52
|
+
* calls it.
|
|
53
|
+
*/
|
|
54
|
+
export declare function terrainImplementation(): Record<string, unknown>;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { exactAcos } from '@driftengine/core';
|
|
2
|
+
import { defineCapability } from 'driftscript';
|
|
3
|
+
export const TERRAIN_MODULE = 'drift/terrain';
|
|
4
|
+
/**
|
|
5
|
+
* `drift/terrain` — where the ground is, and which way it faces.
|
|
6
|
+
*
|
|
7
|
+
* **The linker has refused this module by name since the language shipped**, saying it waits on
|
|
8
|
+
* Track L. It does not any more: `@driftengine/terrain` ships a heightfield whose query answers the
|
|
9
|
+
* surface that is *drawn*, which is the one property a script needs to be able to trust. A script
|
|
10
|
+
* placing a tree, deciding whether a slope is walkable, or dropping a dead body on the ground is
|
|
11
|
+
* asking this module and nothing else.
|
|
12
|
+
*
|
|
13
|
+
* **Every capability here is a read of static data**, so all of them are deterministic and a
|
|
14
|
+
* `@deterministic` system may ask where the ground is. That is the point — deciding where to stand
|
|
15
|
+
* belongs inside the fixed step.
|
|
16
|
+
*
|
|
17
|
+
* ## The effect these are declared under, and why it is not `terrain.read`
|
|
18
|
+
*
|
|
19
|
+
* **The language names no terrain effect.** `Effect` in `driftscript`'s registry runs from `pure`
|
|
20
|
+
* through `network.write`, and there is no `terrain.*` in it — `drift/terrain` is in
|
|
21
|
+
* `SPECIFIED_MODULES` and was missed when the effects were named. `drift/behavior` was in exactly
|
|
22
|
+
* this position and the language fixed it the other way round, naming `behavior.read` and
|
|
23
|
+
* `behavior.write` in 2026-08-28 *ahead of any provider*, with a comment saying it was so the track
|
|
24
|
+
* that built it would not need a language release first. Terrain did not get that.
|
|
25
|
+
*
|
|
26
|
+
* **This engine does not move the language's version**, which the navigation binding states where it
|
|
27
|
+
* meets the same wall. So the choice was between shipping under an effect that exists and not
|
|
28
|
+
* shipping.
|
|
29
|
+
*
|
|
30
|
+
* **`physics.read` is the one it ships under, and it is a description rather than a borrowing.**
|
|
31
|
+
* The terrain package's own tests assert that the surface a query answers, the surface that is
|
|
32
|
+
* drawn and the surface a body collides with are one surface — a ray cast against the mesh built
|
|
33
|
+
* from a patch lands where `heightAt` says the ground is. So "ask the terrain how high the ground
|
|
34
|
+
* is" and "cast a ray down and see what you hit" are the same question about the same surface, and
|
|
35
|
+
* the second is `physics.read` already. It is also in `DETERMINISTIC_EFFECTS`, which is what a
|
|
36
|
+
* script needs.
|
|
37
|
+
*
|
|
38
|
+
* **What would change it**: the language naming `terrain.read`. `docs/IMPROVEMENTS.md` carries that
|
|
39
|
+
* as a one-line change on the language's side and a one-word change here.
|
|
40
|
+
*/
|
|
41
|
+
export const TERRAIN_TYPES = [
|
|
42
|
+
{
|
|
43
|
+
module: TERRAIN_MODULE,
|
|
44
|
+
name: 'Terrain',
|
|
45
|
+
doc: 'A heightfield: ask it how high the ground is and which way it faces.',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
const define = (name, params, returns, doc) => defineCapability({
|
|
49
|
+
module: TERRAIN_MODULE,
|
|
50
|
+
name,
|
|
51
|
+
signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> ${returns}`,
|
|
52
|
+
params: [...params],
|
|
53
|
+
returns,
|
|
54
|
+
effects: ['physics.read'],
|
|
55
|
+
deterministic: true,
|
|
56
|
+
doc,
|
|
57
|
+
implementation: `${TERRAIN_MODULE}.${name}`,
|
|
58
|
+
});
|
|
59
|
+
const AT = [
|
|
60
|
+
{ name: 'terrain', type: 'Terrain' },
|
|
61
|
+
{ name: 'x', type: 'float' },
|
|
62
|
+
{ name: 'z', type: 'float' },
|
|
63
|
+
];
|
|
64
|
+
export const TERRAIN_CAPABILITIES = [
|
|
65
|
+
define('heightAt', AT, 'float', 'How high the ground is at a place, in metres. The surface that is drawn, not an approximation of it, so a thing put here stands on what you can see.'),
|
|
66
|
+
/*
|
|
67
|
+
* **Three calls rather than one that returns a vector**, which is the shape `drift/physics`
|
|
68
|
+
* already uses for a body's position and `drift/navigation` for a steer point: the language
|
|
69
|
+
* returns one value, and the alternative is a search-then-read pair with state between the two.
|
|
70
|
+
* A normal is cheap enough that three independent reads beat a hidden cursor.
|
|
71
|
+
*/
|
|
72
|
+
define('normalX', AT, 'float', 'Which way the ground faces at a place, along x.'),
|
|
73
|
+
define('normalY', AT, 'float', 'The same, along y. One on the flat and smaller on a slope.'),
|
|
74
|
+
define('normalZ', AT, 'float', 'The same, along z.'),
|
|
75
|
+
/*
|
|
76
|
+
* **The slope as an angle, because that is what a script actually asks.** Every use of a terrain
|
|
77
|
+
* normal in a script is "is this too steep" — for a footpath, for a building, for whether a cart
|
|
78
|
+
* rolls — and each of those written from three components is an `acos` of a dot product a script
|
|
79
|
+
* author has to get right. Radians from vertical: zero on the flat.
|
|
80
|
+
*/
|
|
81
|
+
define('slopeAt', AT, 'float', 'How steep the ground is at a place, in radians from flat. Zero on the level; a quarter turn on a wall.'),
|
|
82
|
+
define('covers', AT, 'bool', 'Whether a place is over the field at all. A query outside it answers the nearest edge rather than nothing, so ask this first where it matters.'),
|
|
83
|
+
/*
|
|
84
|
+
* **`f32` rather than `float`, and the language is right to insist.** `float` is width-polymorphic
|
|
85
|
+
* and takes its width from a `float` parameter; these take none, so nothing would fix it. A field
|
|
86
|
+
* is stored as `Float32Array`, so `f32` is what the number actually is rather than a width chosen
|
|
87
|
+
* to satisfy a checker.
|
|
88
|
+
*/
|
|
89
|
+
define('extentX', [{ name: 'terrain', type: 'Terrain' }], 'f32', 'How far the field reaches along x, in metres.'),
|
|
90
|
+
define('extentZ', [{ name: 'terrain', type: 'Terrain' }], 'f32', 'How far the field reaches along z, in metres.'),
|
|
91
|
+
];
|
|
92
|
+
/**
|
|
93
|
+
* **No services, and that is the shape rather than an omission.**
|
|
94
|
+
*
|
|
95
|
+
* Every capability here takes the field it is asking about, so the module needs nothing from the
|
|
96
|
+
* host to answer — the terrain reaches a script the way a `NavGraph` does, as a resource handed in
|
|
97
|
+
* through `uses`. `drift/animation` made the same move on 2026-08-28 when `blendTree` was replaced
|
|
98
|
+
* by `blendSet` and `blendAt`: drive a thing the host built, rather than offering to build one out
|
|
99
|
+
* of values the language has no shape for.
|
|
100
|
+
*
|
|
101
|
+
* So this is registered unconditionally, and a script that never receives a `Terrain` simply never
|
|
102
|
+
* calls it.
|
|
103
|
+
*/
|
|
104
|
+
export function terrainImplementation() {
|
|
105
|
+
/* One scratch for every normal read, because these run inside the fixed step and the frame rules
|
|
106
|
+
forbid allocating there. Reused rather than returned: each capability answers one number. */
|
|
107
|
+
const normal = new Float32Array(3);
|
|
108
|
+
const at = (terrain, x, z) => terrain.normalAt(x, z, normal);
|
|
109
|
+
return {
|
|
110
|
+
heightAt: (terrain, x, z) => terrain.heightAt(x, z),
|
|
111
|
+
normalX: (terrain, x, z) => at(terrain, x, z)[0] ?? 0,
|
|
112
|
+
normalY: (terrain, x, z) => at(terrain, x, z)[1] ?? 1,
|
|
113
|
+
normalZ: (terrain, x, z) => at(terrain, x, z)[2] ?? 0,
|
|
114
|
+
slopeAt: (terrain, x, z) => {
|
|
115
|
+
/*
|
|
116
|
+
* `exactAcos` of the upward component, clamped: a normal is a unit vector in exact arithmetic
|
|
117
|
+
* and a hair outside the domain in float, where `acos` answers NaN rather than zero.
|
|
118
|
+
*
|
|
119
|
+
* **`Math.acos` until 2026-09-03, and that was a defect rather than a tidiness point.**
|
|
120
|
+
* `drift/terrain` ships under `physics.read`, which is inside the language's
|
|
121
|
+
* `DETERMINISTIC_EFFECTS`, so a `@deterministic` system asking a hillside how steep it is was
|
|
122
|
+
* receiving a number ECMAScript does not specify — two engines may answer an ulp apart, and
|
|
123
|
+
* the annotation promising the system replays identically was false for anything that
|
|
124
|
+
* branched on the answer. Found by widening `scripts/determinism.mjs` past the one package it
|
|
125
|
+
* had scanned since Track B.
|
|
126
|
+
*/
|
|
127
|
+
const up = Math.min(1, Math.max(-1, at(terrain, x, z)[1] ?? 1));
|
|
128
|
+
return exactAcos(up);
|
|
129
|
+
},
|
|
130
|
+
covers: (terrain, x, z) => {
|
|
131
|
+
const u = x - (terrain.origin[0] ?? 0);
|
|
132
|
+
const v = z - (terrain.origin[2] ?? 0);
|
|
133
|
+
return u >= 0 && v >= 0 && u <= terrain.extentX && v <= terrain.extentZ;
|
|
134
|
+
},
|
|
135
|
+
extentX: (terrain) => terrain.extentX,
|
|
136
|
+
extentZ: (terrain) => terrain.extentZ,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { type CapabilityDefinition, type OpaqueType } from 'driftscript';
|
|
39
|
+
import type { ControllerState, HandSkeleton, Handedness, XrSupport } from '@driftengine/xr';
|
|
40
|
+
export declare const XR_MODULE = "drift/xr";
|
|
41
|
+
export declare const XR_TYPES: readonly OpaqueType[];
|
|
42
|
+
export declare const XR_CAPABILITIES: readonly CapabilityDefinition[];
|
|
43
|
+
/**
|
|
44
|
+
* What the host keeps up to date, and what a script reads through.
|
|
45
|
+
*
|
|
46
|
+
* The consumer's, exactly as `AgentRegistry` and `SoundRegistry` are. This package owns neither the
|
|
47
|
+
* session nor the frame, and which of them is current is not something a binding can know.
|
|
48
|
+
*/
|
|
49
|
+
export interface XrRuntime {
|
|
50
|
+
readonly support: XrSupport | null;
|
|
51
|
+
/** Null outside a session. */
|
|
52
|
+
readonly headPosition: Float32Array | null;
|
|
53
|
+
controller(hand: Handedness): ControllerState | null;
|
|
54
|
+
hand(hand: Handedness): HandSkeleton | null;
|
|
55
|
+
}
|
|
56
|
+
export declare function xrImplementation(runtime: XrRuntime): Readonly<Record<string, unknown>>;
|