@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,310 @@
1
+ import type { SpriteBatch, SpriteSheet, Tilemap } from '@driftengine/ui2d';
2
+ import {
3
+ createSpriteFrame,
4
+ drawSprite,
5
+ drawTilemap,
6
+ frameOf,
7
+ setTile,
8
+ sheetFrame,
9
+ tileAt,
10
+ } from '@driftengine/ui2d';
11
+ import type { CapabilityDefinition, Effect, OpaqueType } from 'driftscript';
12
+ import { defineCapability } from 'driftscript';
13
+
14
+ export const SPRITES_MODULE = 'drift/2d';
15
+
16
+ /**
17
+ * `drift/2d` — quads with a picture on them, and the grids of them.
18
+ *
19
+ * ```drs
20
+ * import { sprite, tilemap } from "drift/2d" as sprites
21
+ *
22
+ * fn hud(batch: SpriteBatch) {
23
+ * sprites.sprite(batch, 0, 10, 10, 32, 32)
24
+ * }
25
+ * ```
26
+ *
27
+ * **The `as` is not decoration and this module is why the language has it.** A capability is reached
28
+ * through a namespace and the namespace was the last segment of the path, so a call here would have
29
+ * been `2d.sprite(...)` — a number followed by an identifier, refused by the lexer before the
30
+ * checker saw it. This binding was written on 2026-09-03, wired, found uncallable, and **withdrawn
31
+ * rather than shipped**, because a capability nobody can spell is the silent no-op this repository
32
+ * forbids wearing a feature's clothes. DriftScript 1.11.0 answered it at the import, and the
33
+ * language's own changelog carries the five spellings that were tried and failed.
34
+ *
35
+ * So a script naming this module must name the namespace too, and `DS0139` says so at the import
36
+ * with the line to write rather than leaving an author at `DS0003` inside their own call.
37
+ *
38
+ * ## The effects, and why this one needed nothing from the language
39
+ *
40
+ * Unlike `drift/terrain`, this module met no wall on effects. **A sprite is a thing that draws**,
41
+ * and `scene.write` is exactly what the language reserves for that — its own comment says a
42
+ * `SceneNode` "is what *draws*", and that writing one is a change to the view rather than to the
43
+ * simulation. Everything here that puts a quad in a batch is declared that way, and it is outside
44
+ * `DETERMINISTIC_EFFECTS` for the right reason: a `@deterministic` system decides where things
45
+ * *are*, and something else draws them.
46
+ *
47
+ * **Reading a sheet or a tile is `scene.read`**, which is inside the boundary, so a deterministic
48
+ * system may ask what frame a name is or what is in a cell. That asymmetry is the honest one:
49
+ * looking at the view is a read of state the simulation already produced.
50
+ *
51
+ * **`setTile` is a write to the view and is outside**, which is worth being explicit about because
52
+ * it reads as a limitation and is not one. A `Tilemap` here is *drawing* data — its cells are frame
53
+ * indices into a sheet, and `tilemap.ts` says so in its first line. A game's collision grid is the
54
+ * game's own array and is not this. A deterministic system that wants to dig a hole changes its own
55
+ * grid and lets a drawing system follow.
56
+ */
57
+ export const SPRITES_TYPES: readonly OpaqueType[] = [
58
+ {
59
+ module: SPRITES_MODULE,
60
+ name: 'SpriteBatch',
61
+ doc: 'One frame of quads, in the order they were put in. Reset it, fill it, draw it.',
62
+ },
63
+ {
64
+ module: SPRITES_MODULE,
65
+ name: 'SpriteSheet',
66
+ doc: 'The rectangles of one texture that each hold a picture, addressed by index or by name.',
67
+ },
68
+ {
69
+ module: SPRITES_MODULE,
70
+ name: 'Tilemap',
71
+ doc: 'A grid of sheet frames. Drawing one costs the view rather than the map.',
72
+ },
73
+ ];
74
+
75
+ const define = (
76
+ name: string,
77
+ params: readonly { name: string; type: string }[],
78
+ returns: string,
79
+ effects: readonly Effect[],
80
+ doc: string,
81
+ ): CapabilityDefinition =>
82
+ defineCapability({
83
+ module: SPRITES_MODULE,
84
+ name,
85
+ signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> ${returns}`,
86
+ params: [...params],
87
+ returns,
88
+ effects: [...effects],
89
+ /*
90
+ * Every float here is `f32` and none is `float`. `float` is width-polymorphic and takes its
91
+ * width from another `float`, so a capability returning one must take one — a rule Track L paid
92
+ * for. Nothing here returns a float at all, and a batch stores its instances as `Float32Array`,
93
+ * so `f32` is what the number actually is rather than a width chosen to satisfy a checker.
94
+ */
95
+ deterministic: !effects.includes('scene.write'),
96
+ doc,
97
+ implementation: `${SPRITES_MODULE}.${name}`,
98
+ });
99
+
100
+ const AT = [
101
+ { name: 'x', type: 'f32' },
102
+ { name: 'y', type: 'f32' },
103
+ { name: 'w', type: 'f32' },
104
+ { name: 'h', type: 'f32' },
105
+ ] as const;
106
+
107
+ export const SPRITES_CAPABILITIES: readonly CapabilityDefinition[] = [
108
+ define(
109
+ 'sprite',
110
+ [{ name: 'batch', type: 'SpriteBatch' }, { name: 'texture', type: 'i32' }, ...AT],
111
+ 'void',
112
+ ['scene.write'],
113
+ 'Put one quad in the batch, covering the whole of a texture.',
114
+ ),
115
+ define(
116
+ 'tinted',
117
+ [
118
+ { name: 'batch', type: 'SpriteBatch' },
119
+ { name: 'texture', type: 'i32' },
120
+ ...AT,
121
+ { name: 'r', type: 'f32' },
122
+ { name: 'g', type: 'f32' },
123
+ { name: 'b', type: 'f32' },
124
+ { name: 'a', type: 'f32' },
125
+ ],
126
+ 'void',
127
+ ['scene.write'],
128
+ '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.',
129
+ ),
130
+ define(
131
+ 'frame',
132
+ [
133
+ { name: 'batch', type: 'SpriteBatch' },
134
+ { name: 'sheet', type: 'SpriteSheet' },
135
+ { name: 'frame', type: 'i32' },
136
+ ...AT,
137
+ ],
138
+ 'void',
139
+ ['scene.write'],
140
+ 'Put one frame of a sheet in the batch. A frame the sheet does not have draws the whole sheet, which is unmistakable.',
141
+ ),
142
+ define(
143
+ 'named',
144
+ [
145
+ { name: 'sheet', type: 'SpriteSheet' },
146
+ { name: 'name', type: 'String' },
147
+ ],
148
+ 'i32',
149
+ ['scene.read'],
150
+ '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.',
151
+ ),
152
+ define(
153
+ 'frames',
154
+ [{ name: 'sheet', type: 'SpriteSheet' }],
155
+ 'i32',
156
+ ['scene.read'],
157
+ 'How many frames the sheet was cut into.',
158
+ ),
159
+ define(
160
+ 'tilemap',
161
+ [
162
+ { name: 'batch', type: 'SpriteBatch' },
163
+ { name: 'map', type: 'Tilemap' },
164
+ { name: 'sheet', type: 'SpriteSheet' },
165
+ ...AT,
166
+ ],
167
+ 'i32',
168
+ ['scene.write'],
169
+ 'Draw the tiles a view rectangle can see, and answer how many that was. The cost is the view rather than the map.',
170
+ ),
171
+ define(
172
+ 'tile',
173
+ [
174
+ { name: 'map', type: 'Tilemap' },
175
+ { name: 'column', type: 'i32' },
176
+ { name: 'row', type: 'i32' },
177
+ ],
178
+ 'i32',
179
+ ['scene.read'],
180
+ '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.',
181
+ ),
182
+ define(
183
+ 'setTile',
184
+ [
185
+ { name: 'map', type: 'Tilemap' },
186
+ { name: 'column', type: 'i32' },
187
+ { name: 'row', type: 'i32' },
188
+ { name: 'tile', type: 'i32' },
189
+ ],
190
+ 'void',
191
+ ['scene.write'],
192
+ 'Put a frame in a cell. A cell outside the map is ignored rather than wrapping into another row.',
193
+ ),
194
+ define(
195
+ 'columns',
196
+ [{ name: 'map', type: 'Tilemap' }],
197
+ 'i32',
198
+ ['scene.read'],
199
+ 'How wide the map is, in cells.',
200
+ ),
201
+ define(
202
+ 'rows',
203
+ [{ name: 'map', type: 'Tilemap' }],
204
+ 'i32',
205
+ ['scene.read'],
206
+ 'How tall the map is, in cells.',
207
+ ),
208
+ define(
209
+ 'count',
210
+ [{ name: 'batch', type: 'SpriteBatch' }],
211
+ 'i32',
212
+ ['scene.read'],
213
+ 'How many quads are in the batch this frame.',
214
+ ),
215
+ /*
216
+ * **A script can see the batch overflow, which is the whole reason this is exposed.** The batch
217
+ * does not grow: past its capacity a draw is counted and dropped, and a caller with no way to
218
+ * read that count finds out by noticing something missing from a corner of the screen.
219
+ */
220
+ define(
221
+ 'dropped',
222
+ [{ name: 'batch', type: 'SpriteBatch' }],
223
+ 'i32',
224
+ ['scene.read'],
225
+ 'How many quads did not fit this frame. Zero is the only good value; anything else is a batch that wants a bigger capacity.',
226
+ ),
227
+ ];
228
+
229
+ /**
230
+ * **No services, and that is the shape rather than an omission.**
231
+ *
232
+ * Every capability takes the batch, sheet or map it acts on, so the module needs nothing from the
233
+ * host to answer — they reach a script through `uses`, the way a `Terrain` and a `NavGraph` do. So
234
+ * this is registered unconditionally, and a script that never receives a batch never calls it.
235
+ */
236
+ export function spritesImplementation(): Record<string, unknown> {
237
+ /* One frame rectangle for every `frame` call. These run per draw and may not allocate. */
238
+ const scratch = createSpriteFrame();
239
+ const place = { x: 0, y: 0, w: 0, h: 0 };
240
+ const tint = new Float32Array(4);
241
+ const view = { x: 0, y: 0, w: 0, h: 0 };
242
+
243
+ const at = (x: number, y: number, w: number, h: number): typeof place => {
244
+ place.x = x;
245
+ place.y = y;
246
+ place.w = w;
247
+ place.h = h;
248
+ return place;
249
+ };
250
+
251
+ return {
252
+ sprite: (batch: SpriteBatch, texture: number, x: number, y: number, w: number, h: number) => {
253
+ drawSprite(batch, texture, at(x, y, w, h), null, null);
254
+ },
255
+ tinted: (
256
+ batch: SpriteBatch,
257
+ texture: number,
258
+ x: number,
259
+ y: number,
260
+ w: number,
261
+ h: number,
262
+ r: number,
263
+ g: number,
264
+ b: number,
265
+ a: number,
266
+ ) => {
267
+ tint[0] = r;
268
+ tint[1] = g;
269
+ tint[2] = b;
270
+ tint[3] = a;
271
+ drawSprite(batch, texture, at(x, y, w, h), null, tint);
272
+ },
273
+ frame: (
274
+ batch: SpriteBatch,
275
+ sheet: SpriteSheet,
276
+ frame: number,
277
+ x: number,
278
+ y: number,
279
+ w: number,
280
+ h: number,
281
+ ) => {
282
+ drawSprite(batch, sheet.texture, at(x, y, w, h), sheetFrame(sheet, frame, scratch), null);
283
+ },
284
+ named: (sheet: SpriteSheet, name: string) => frameOf(sheet, name),
285
+ frames: (sheet: SpriteSheet) => sheet.count,
286
+ tilemap: (
287
+ batch: SpriteBatch,
288
+ map: Tilemap,
289
+ sheet: SpriteSheet,
290
+ x: number,
291
+ y: number,
292
+ w: number,
293
+ h: number,
294
+ ) => {
295
+ view.x = x;
296
+ view.y = y;
297
+ view.w = w;
298
+ view.h = h;
299
+ return drawTilemap(batch, map, sheet, view, null);
300
+ },
301
+ tile: (map: Tilemap, column: number, row: number) => tileAt(map, column, row),
302
+ setTile: (map: Tilemap, column: number, row: number, tile: number) => {
303
+ setTile(map, column, row, tile);
304
+ },
305
+ columns: (map: Tilemap) => map.columns,
306
+ rows: (map: Tilemap) => map.rows,
307
+ count: (batch: SpriteBatch) => batch.count,
308
+ dropped: (batch: SpriteBatch) => batch.dropped,
309
+ };
310
+ }
@@ -0,0 +1,180 @@
1
+ import { exactAcos } from '@driftengine/core';
2
+ import type { Terrain } from '@driftengine/terrain';
3
+ import type { CapabilityDefinition, OpaqueType } from 'driftscript';
4
+ import { defineCapability } from 'driftscript';
5
+
6
+ export const TERRAIN_MODULE = 'drift/terrain';
7
+
8
+ /**
9
+ * `drift/terrain` — where the ground is, and which way it faces.
10
+ *
11
+ * **The linker has refused this module by name since the language shipped**, saying it waits on
12
+ * Track L. It does not any more: `@driftengine/terrain` ships a heightfield whose query answers the
13
+ * surface that is *drawn*, which is the one property a script needs to be able to trust. A script
14
+ * placing a tree, deciding whether a slope is walkable, or dropping a dead body on the ground is
15
+ * asking this module and nothing else.
16
+ *
17
+ * **Every capability here is a read of static data**, so all of them are deterministic and a
18
+ * `@deterministic` system may ask where the ground is. That is the point — deciding where to stand
19
+ * belongs inside the fixed step.
20
+ *
21
+ * ## The effect these are declared under, and why it is not `terrain.read`
22
+ *
23
+ * **The language names no terrain effect.** `Effect` in `driftscript`'s registry runs from `pure`
24
+ * through `network.write`, and there is no `terrain.*` in it — `drift/terrain` is in
25
+ * `SPECIFIED_MODULES` and was missed when the effects were named. `drift/behavior` was in exactly
26
+ * this position and the language fixed it the other way round, naming `behavior.read` and
27
+ * `behavior.write` in 2026-08-28 *ahead of any provider*, with a comment saying it was so the track
28
+ * that built it would not need a language release first. Terrain did not get that.
29
+ *
30
+ * **This engine does not move the language's version**, which the navigation binding states where it
31
+ * meets the same wall. So the choice was between shipping under an effect that exists and not
32
+ * shipping.
33
+ *
34
+ * **`physics.read` is the one it ships under, and it is a description rather than a borrowing.**
35
+ * The terrain package's own tests assert that the surface a query answers, the surface that is
36
+ * drawn and the surface a body collides with are one surface — a ray cast against the mesh built
37
+ * from a patch lands where `heightAt` says the ground is. So "ask the terrain how high the ground
38
+ * is" and "cast a ray down and see what you hit" are the same question about the same surface, and
39
+ * the second is `physics.read` already. It is also in `DETERMINISTIC_EFFECTS`, which is what a
40
+ * script needs.
41
+ *
42
+ * **What would change it**: the language naming `terrain.read`. `docs/IMPROVEMENTS.md` carries that
43
+ * as a one-line change on the language's side and a one-word change here.
44
+ */
45
+ export const TERRAIN_TYPES: readonly OpaqueType[] = [
46
+ {
47
+ module: TERRAIN_MODULE,
48
+ name: 'Terrain',
49
+ doc: 'A heightfield: ask it how high the ground is and which way it faces.',
50
+ },
51
+ ];
52
+
53
+ const define = (
54
+ name: string,
55
+ params: readonly { name: string; type: string }[],
56
+ returns: string,
57
+ doc: string,
58
+ ): CapabilityDefinition =>
59
+ defineCapability({
60
+ module: TERRAIN_MODULE,
61
+ name,
62
+ signature: `fn(${params.map((p) => `${p.name}: ${p.type}`).join(', ')}) -> ${returns}`,
63
+ params: [...params],
64
+ returns,
65
+ effects: ['physics.read'],
66
+ deterministic: true,
67
+ doc,
68
+ implementation: `${TERRAIN_MODULE}.${name}`,
69
+ });
70
+
71
+ const AT = [
72
+ { name: 'terrain', type: 'Terrain' },
73
+ { name: 'x', type: 'float' },
74
+ { name: 'z', type: 'float' },
75
+ ] as const;
76
+
77
+ export const TERRAIN_CAPABILITIES: readonly CapabilityDefinition[] = [
78
+ define(
79
+ 'heightAt',
80
+ AT,
81
+ 'float',
82
+ '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.',
83
+ ),
84
+ /*
85
+ * **Three calls rather than one that returns a vector**, which is the shape `drift/physics`
86
+ * already uses for a body's position and `drift/navigation` for a steer point: the language
87
+ * returns one value, and the alternative is a search-then-read pair with state between the two.
88
+ * A normal is cheap enough that three independent reads beat a hidden cursor.
89
+ */
90
+ define('normalX', AT, 'float', 'Which way the ground faces at a place, along x.'),
91
+ define('normalY', AT, 'float', 'The same, along y. One on the flat and smaller on a slope.'),
92
+ define('normalZ', AT, 'float', 'The same, along z.'),
93
+ /*
94
+ * **The slope as an angle, because that is what a script actually asks.** Every use of a terrain
95
+ * normal in a script is "is this too steep" — for a footpath, for a building, for whether a cart
96
+ * rolls — and each of those written from three components is an `acos` of a dot product a script
97
+ * author has to get right. Radians from vertical: zero on the flat.
98
+ */
99
+ define(
100
+ 'slopeAt',
101
+ AT,
102
+ 'float',
103
+ 'How steep the ground is at a place, in radians from flat. Zero on the level; a quarter turn on a wall.',
104
+ ),
105
+ define(
106
+ 'covers',
107
+ AT,
108
+ 'bool',
109
+ '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.',
110
+ ),
111
+ /*
112
+ * **`f32` rather than `float`, and the language is right to insist.** `float` is width-polymorphic
113
+ * and takes its width from a `float` parameter; these take none, so nothing would fix it. A field
114
+ * is stored as `Float32Array`, so `f32` is what the number actually is rather than a width chosen
115
+ * to satisfy a checker.
116
+ */
117
+ define(
118
+ 'extentX',
119
+ [{ name: 'terrain', type: 'Terrain' }],
120
+ 'f32',
121
+ 'How far the field reaches along x, in metres.',
122
+ ),
123
+ define(
124
+ 'extentZ',
125
+ [{ name: 'terrain', type: 'Terrain' }],
126
+ 'f32',
127
+ 'How far the field reaches along z, in metres.',
128
+ ),
129
+ ];
130
+
131
+ /**
132
+ * **No services, and that is the shape rather than an omission.**
133
+ *
134
+ * Every capability here takes the field it is asking about, so the module needs nothing from the
135
+ * host to answer — the terrain reaches a script the way a `NavGraph` does, as a resource handed in
136
+ * through `uses`. `drift/animation` made the same move on 2026-08-28 when `blendTree` was replaced
137
+ * by `blendSet` and `blendAt`: drive a thing the host built, rather than offering to build one out
138
+ * of values the language has no shape for.
139
+ *
140
+ * So this is registered unconditionally, and a script that never receives a `Terrain` simply never
141
+ * calls it.
142
+ */
143
+ export function terrainImplementation(): Record<string, unknown> {
144
+ /* One scratch for every normal read, because these run inside the fixed step and the frame rules
145
+ forbid allocating there. Reused rather than returned: each capability answers one number. */
146
+ const normal = new Float32Array(3);
147
+
148
+ const at = (terrain: Terrain, x: number, z: number): Float32Array =>
149
+ terrain.normalAt(x, z, normal);
150
+
151
+ return {
152
+ heightAt: (terrain: Terrain, x: number, z: number) => terrain.heightAt(x, z),
153
+ normalX: (terrain: Terrain, x: number, z: number) => at(terrain, x, z)[0] ?? 0,
154
+ normalY: (terrain: Terrain, x: number, z: number) => at(terrain, x, z)[1] ?? 1,
155
+ normalZ: (terrain: Terrain, x: number, z: number) => at(terrain, x, z)[2] ?? 0,
156
+ slopeAt: (terrain: Terrain, x: number, z: number) => {
157
+ /*
158
+ * `exactAcos` of the upward component, clamped: a normal is a unit vector in exact arithmetic
159
+ * and a hair outside the domain in float, where `acos` answers NaN rather than zero.
160
+ *
161
+ * **`Math.acos` until 2026-09-03, and that was a defect rather than a tidiness point.**
162
+ * `drift/terrain` ships under `physics.read`, which is inside the language's
163
+ * `DETERMINISTIC_EFFECTS`, so a `@deterministic` system asking a hillside how steep it is was
164
+ * receiving a number ECMAScript does not specify — two engines may answer an ulp apart, and
165
+ * the annotation promising the system replays identically was false for anything that
166
+ * branched on the answer. Found by widening `scripts/determinism.mjs` past the one package it
167
+ * had scanned since Track B.
168
+ */
169
+ const up = Math.min(1, Math.max(-1, at(terrain, x, z)[1] ?? 1));
170
+ return exactAcos(up);
171
+ },
172
+ covers: (terrain: Terrain, x: number, z: number) => {
173
+ const u = x - (terrain.origin[0] ?? 0);
174
+ const v = z - (terrain.origin[2] ?? 0);
175
+ return u >= 0 && v >= 0 && u <= terrain.extentX && v <= terrain.extentZ;
176
+ },
177
+ extentX: (terrain: Terrain) => terrain.extentX,
178
+ extentZ: (terrain: Terrain) => terrain.extentZ,
179
+ };
180
+ }