@defold-typescript/types 0.50.0 → 0.51.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 (88) hide show
  1. package/api-signatures.json +286 -232
  2. package/examples/translations.json +15 -15
  3. package/fixtures/messages_doc.json +20 -39
  4. package/generated/builtin-messages.d.ts +16 -14
  5. package/generated/collectionfactory.d.ts +1 -1
  6. package/generated/collectionproxy.d.ts +2 -2
  7. package/generated/factory.d.ts +4 -2
  8. package/generated/graphics.d.ts +4 -0
  9. package/generated/gui.d.ts +2 -2
  10. package/generated/http.d.ts +3 -1
  11. package/generated/image.d.ts +1 -1
  12. package/generated/kinds/gui-script.d.ts +1 -0
  13. package/generated/kinds/render-script.d.ts +1 -0
  14. package/generated/kinds/script.d.ts +1 -0
  15. package/generated/model.d.ts +4 -5
  16. package/generated/physics.d.ts +3 -3
  17. package/generated/render.d.ts +41 -13
  18. package/generated/sys.d.ts +8 -4
  19. package/generated/tilemap.d.ts +4 -4
  20. package/generated/versions/defold-1.12.4/collectionfactory.d.ts +1 -1
  21. package/generated/versions/defold-1.12.4/collectionproxy.d.ts +1 -1
  22. package/generated/versions/defold-1.12.4/factory.d.ts +4 -2
  23. package/generated/versions/defold-1.12.4/gui.d.ts +2 -2
  24. package/generated/versions/defold-1.12.4/http.d.ts +3 -1
  25. package/generated/versions/defold-1.12.4/image.d.ts +1 -1
  26. package/generated/versions/defold-1.12.4/index.d.ts +1 -0
  27. package/generated/versions/defold-1.12.4/model.d.ts +4 -5
  28. package/generated/versions/defold-1.12.4/physics.d.ts +2 -2
  29. package/generated/versions/defold-1.12.4/render.d.ts +41 -13
  30. package/generated/versions/defold-1.12.4/sys.d.ts +1 -1
  31. package/generated/versions/defold-1.12.4/tilemap.d.ts +4 -4
  32. package/generated/versions/defold-1.13.0/collectionfactory.d.ts +1 -1
  33. package/generated/versions/defold-1.13.0/collectionproxy.d.ts +1 -1
  34. package/generated/versions/defold-1.13.0/factory.d.ts +4 -2
  35. package/generated/versions/defold-1.13.0/gui.d.ts +2 -2
  36. package/generated/versions/defold-1.13.0/http.d.ts +3 -1
  37. package/generated/versions/defold-1.13.0/image.d.ts +1 -1
  38. package/generated/versions/defold-1.13.0/index.d.ts +1 -0
  39. package/generated/versions/defold-1.13.0/model.d.ts +4 -5
  40. package/generated/versions/defold-1.13.0/physics.d.ts +2 -2
  41. package/generated/versions/defold-1.13.0/render.d.ts +41 -13
  42. package/generated/versions/defold-1.13.0/sys.d.ts +1 -1
  43. package/generated/versions/defold-1.13.0/tilemap.d.ts +4 -4
  44. package/generated/versions/defold-1.13.1/collectionfactory.d.ts +1 -1
  45. package/generated/versions/defold-1.13.1/collectionproxy.d.ts +1 -1
  46. package/generated/versions/defold-1.13.1/factory.d.ts +4 -2
  47. package/generated/versions/defold-1.13.1/gui.d.ts +2 -2
  48. package/generated/versions/defold-1.13.1/http.d.ts +3 -1
  49. package/generated/versions/defold-1.13.1/image.d.ts +1 -1
  50. package/generated/versions/defold-1.13.1/index.d.ts +1 -0
  51. package/generated/versions/defold-1.13.1/model.d.ts +4 -5
  52. package/generated/versions/defold-1.13.1/physics.d.ts +2 -2
  53. package/generated/versions/defold-1.13.1/render.d.ts +41 -13
  54. package/generated/versions/defold-1.13.1/sys.d.ts +1 -1
  55. package/generated/versions/defold-1.13.1/tilemap.d.ts +4 -4
  56. package/generated/versions/defold-1.13.2/collectionfactory.d.ts +1 -1
  57. package/generated/versions/defold-1.13.2/collectionproxy.d.ts +2 -2
  58. package/generated/versions/defold-1.13.2/factory.d.ts +4 -2
  59. package/generated/versions/defold-1.13.2/gui.d.ts +2 -2
  60. package/generated/versions/defold-1.13.2/http.d.ts +3 -1
  61. package/generated/versions/defold-1.13.2/image.d.ts +1 -1
  62. package/generated/versions/defold-1.13.2/index.d.ts +1 -0
  63. package/generated/versions/defold-1.13.2/model.d.ts +4 -5
  64. package/generated/versions/defold-1.13.2/physics.d.ts +3 -3
  65. package/generated/versions/defold-1.13.2/render.d.ts +41 -13
  66. package/generated/versions/defold-1.13.2/sys.d.ts +8 -4
  67. package/generated/versions/defold-1.13.2/tilemap.d.ts +4 -4
  68. package/generated/versions/defold-1.13.2/window.d.ts +1 -1
  69. package/generated/window.d.ts +1 -1
  70. package/identity-vocabulary.json +46 -0
  71. package/index.d.ts +1 -0
  72. package/package.json +2 -1
  73. package/scripts/generate-api-availability.ts +5 -1
  74. package/scripts/generate-api-signatures.ts +16 -11
  75. package/scripts/materialize-version.ts +7 -1
  76. package/scripts/regen.ts +42 -25
  77. package/src/component-properties.d.ts +137 -6
  78. package/src/core-types.ts +46 -11
  79. package/src/emit-dts.ts +555 -84
  80. package/src/emit-messages.ts +289 -0
  81. package/src/engine-globals.d.ts +3 -1
  82. package/src/go-overloads.d.ts +13 -3
  83. package/src/lifecycle.ts +16 -16
  84. package/src/luals-type-expr.ts +6 -1
  85. package/src/physics-event-guard.d.ts +39 -0
  86. package/src/render-overloads.d.ts +2 -2
  87. package/src/return-correction-provenance.ts +36 -6
  88. package/src/window-event-guard.d.ts +5 -3
@@ -137,6 +137,295 @@ export function applyMessageFieldCorrections(
137
137
  return { entries };
138
138
  }
139
139
 
140
+ // What only the engine states about a message the catalog mirrors from the
141
+ // reference: proto optionality, a field the handler reads or the sender fills,
142
+ // a message a component handles or posts, or the shape of a field the reference
143
+ // types only as `table`. `evidence` names `<path>:<symbol>` in the 1.14.0 engine
144
+ // source. Each kind throws once the catalog already states its fact, since that
145
+ // means upstream fixed the reference and the entry is deleted; a `retype` throws
146
+ // once the catalog stops declaring its pinned `upstream` types, since upstream
147
+ // retyped the field and the entry is re-verified against what it now says.
148
+ export type MessageEngineCorrection = {
149
+ readonly origin: string;
150
+ readonly name: string;
151
+ readonly evidence: string;
152
+ } & (
153
+ | { readonly kind: "optional" | "required"; readonly fields: readonly string[] }
154
+ | { readonly kind: "add-field"; readonly fields: readonly MessageField[] }
155
+ | {
156
+ readonly kind: "add-message";
157
+ readonly description: string;
158
+ readonly fields: readonly MessageField[];
159
+ }
160
+ | {
161
+ readonly kind: "retype";
162
+ readonly fields: readonly string[];
163
+ readonly tsType: string;
164
+ readonly upstream: readonly string[];
165
+ }
166
+ );
167
+
168
+ const SCRIPT_DDF_DEFAULTS =
169
+ "script/src/script_ddf.cpp:DoLuaTableToDDF fills an omitted optional field with its default";
170
+ const SOUND_EVENT =
171
+ "gamesys/src/gamesys/components/comp_sound.cpp:DispatchSoundEvent always sets play_id, and HandleEntryFinishedPlaying sends the event only for a play_sound that supplied one";
172
+
173
+ function optionalField(name: string, types: string[], doc: string): MessageField {
174
+ return { name, types, optional: true, doc };
175
+ }
176
+
177
+ function requiredField(name: string, types: string[], doc: string): MessageField {
178
+ return { name, types, optional: false, doc };
179
+ }
180
+
181
+ // The listener and `on_message` paths agree: the DDF the message carries
182
+ // declares the same sub-message fields the listener push sets.
183
+ function physicsBody(message: string): string {
184
+ return `gamesys/proto/gamesys/physics_ddf.proto:${message} declares the same required fields for the on_message payload`;
185
+ }
186
+
187
+ function otherGroupField(): MessageField {
188
+ return {
189
+ name: "group",
190
+ types: ["hash"],
191
+ optional: false,
192
+ doc: "Collision group of the other object, equal to other_group.",
193
+ };
194
+ }
195
+
196
+ export const MESSAGE_ENGINE_CORRECTIONS: readonly MessageEngineCorrection[] = [
197
+ {
198
+ kind: "optional",
199
+ origin: "camera",
200
+ name: "set_camera",
201
+ fields: ["orthographic_projection", "orthographic_zoom", "orthographic_mode"],
202
+ evidence: `gamesys/proto/gamesys/camera_ddf.proto:SetCamera declares them optional with defaults, CompCameraOnMessage handles it, and ${SCRIPT_DDF_DEFAULTS}`,
203
+ },
204
+ {
205
+ kind: "optional",
206
+ origin: "sys",
207
+ name: "reboot",
208
+ fields: ["arg1", "arg2", "arg3", "arg4", "arg5", "arg6"],
209
+ evidence: `script/src/script/sys_ddf.proto:Reboot declares every argument optional, engine/src/engine.cpp:Dispatch handles it, and ${SCRIPT_DDF_DEFAULTS}`,
210
+ },
211
+ {
212
+ kind: "optional",
213
+ origin: "sys",
214
+ name: "start_record",
215
+ fields: ["frame_period", "fps"],
216
+ evidence: `script/src/script/sys_ddf.proto:StartRecord declares both optional with defaults 2 and 30, engine/src/engine.cpp:Dispatch handles it, and ${SCRIPT_DDF_DEFAULTS}`,
217
+ },
218
+ {
219
+ kind: "optional",
220
+ origin: "sprite",
221
+ name: "play_animation",
222
+ fields: ["offset", "playback_rate"],
223
+ evidence: `gamesys/proto/gamesys/sprite_ddf.proto:PlayAnimation declares both optional with defaults 0 and 1, comp_sprite.cpp:CompSpriteOnMessage passes both to PlayAnimation, and ${SCRIPT_DDF_DEFAULTS}`,
224
+ },
225
+ {
226
+ kind: "optional",
227
+ origin: "go",
228
+ name: "set_parent",
229
+ fields: ["parent_id", "keep_world_transform"],
230
+ evidence: `gameobject/proto/gameobject/gameobject_ddf.proto:SetParent declares both optional with defaults 0 and 1, gameobject.cpp:DispatchMessagesFunction handles it, and ${SCRIPT_DDF_DEFAULTS}`,
231
+ },
232
+ {
233
+ kind: "required",
234
+ origin: "sound",
235
+ name: "sound_done",
236
+ fields: ["play_id"],
237
+ evidence: SOUND_EVENT,
238
+ },
239
+ {
240
+ kind: "required",
241
+ origin: "sound",
242
+ name: "sound_stopped",
243
+ fields: ["play_id"],
244
+ evidence: SOUND_EVENT,
245
+ },
246
+ {
247
+ kind: "add-field",
248
+ origin: "sound",
249
+ name: "play_sound",
250
+ fields: [
251
+ optionalField("pan", ["number"], "Pan between -1 and 1, added to the component's pan."),
252
+ optionalField("speed", ["number"], "Speed factor, multiplied with the component's speed."),
253
+ ],
254
+ evidence:
255
+ "gamesys/proto/gamesys/gamesys_ddf.proto:PlaySound declares pan and speed, and comp_sound.cpp:CompSoundOnMessage applies both to the voice",
256
+ },
257
+ {
258
+ kind: "add-field",
259
+ origin: "sound",
260
+ name: "stop_sound",
261
+ fields: [
262
+ optionalField(
263
+ "play_id",
264
+ ["number"],
265
+ "Id passed to play_sound; stops only that voice. Omit to stop every voice.",
266
+ ),
267
+ ],
268
+ evidence:
269
+ "gamesys/proto/gamesys/gamesys_ddf.proto:StopSound declares play_id, and comp_sound.cpp:CompSoundOnMessage stops only the voice that matches it",
270
+ },
271
+ {
272
+ kind: "add-message",
273
+ origin: "sound",
274
+ name: "pause_sound",
275
+ description: "Pause or resume every voice the receiving sound component plays.",
276
+ fields: [optionalField("pause", ["boolean"], "true (default) to pause, false to resume.")],
277
+ evidence:
278
+ "gamesys/proto/gamesys/gamesys_ddf.proto:PauseSound declares pause, and comp_sound.cpp:CompSoundOnMessage pauses every voice of the component",
279
+ },
280
+ {
281
+ kind: "add-message",
282
+ origin: "sound",
283
+ name: "set_pan",
284
+ description: "Set the pan on the receiving sound component.",
285
+ fields: [optionalField("pan", ["number"], "Pan between -1 and 1, default 0.")],
286
+ evidence:
287
+ "gamesys/proto/gamesys/gamesys_ddf.proto:SetPan declares pan, and comp_sound.cpp:CompSoundOnMessage sets the component's pan",
288
+ },
289
+ {
290
+ kind: "add-message",
291
+ origin: "render",
292
+ name: "set_view_projection",
293
+ description:
294
+ "Posted automatically by camera components to the render script with the active view and projection matrices.",
295
+ fields: [
296
+ requiredField("id", ["hash"], "Id of the camera component that posted the matrices."),
297
+ requiredField("view", ["matrix4"], "View matrix of the camera."),
298
+ requiredField("projection", ["matrix4"], "Projection matrix of the camera."),
299
+ ],
300
+ evidence:
301
+ "gamesys/proto/gamesys/gamesys_ddf.proto:SetViewProjection declares id, view and projection required, and comp_camera.cpp:PostRenderScriptSetViewProjectionMsg posts it to the render socket on every camera update",
302
+ },
303
+ {
304
+ kind: "add-field",
305
+ origin: "physics",
306
+ name: "collision_response",
307
+ fields: [otherGroupField()],
308
+ evidence:
309
+ "gamesys/proto/gamesys/physics_ddf.proto:CollisionResponse declares a required group, and comp_collision_object.cpp:CollisionCallback sets it to the other object's group",
310
+ },
311
+ {
312
+ kind: "add-field",
313
+ origin: "physics",
314
+ name: "contact_point_response",
315
+ fields: [otherGroupField()],
316
+ evidence:
317
+ "gamesys/proto/gamesys/physics_ddf.proto:ContactPointResponse declares a required group, and comp_collision_object.cpp:ContactPointCallback sets it to the other object's group",
318
+ },
319
+ {
320
+ kind: "add-field",
321
+ origin: "physics",
322
+ name: "trigger_response",
323
+ fields: [otherGroupField()],
324
+ evidence:
325
+ "gamesys/proto/gamesys/physics_ddf.proto:TriggerResponse declares a required group, and comp_collision_object.cpp:TriggerEnteredCallback sets it to the other object's group",
326
+ },
327
+ {
328
+ kind: "retype",
329
+ origin: "physics",
330
+ name: "contact_point_event",
331
+ fields: ["a", "b"],
332
+ tsType:
333
+ "{ position: Vector3; instance_position: Vector3; normal: Vector3; relative_velocity: Vector3; mass: number; id: Hash; group: Hash }",
334
+ upstream: ["table"],
335
+ evidence: `gamesys/src/gamesys/scripts/script_physics.cpp:PushContactPoint sets these seven fields on each body, and ${physicsBody("ContactPoint")}`,
336
+ },
337
+ {
338
+ kind: "retype",
339
+ origin: "physics",
340
+ name: "collision_event",
341
+ fields: ["a", "b"],
342
+ tsType: "{ position: Vector3; id: Hash; group: Hash }",
343
+ upstream: ["table"],
344
+ evidence: `gamesys/src/gamesys/scripts/script_physics.cpp:PushCollision sets these three fields on each body, and ${physicsBody("Collision")}`,
345
+ },
346
+ {
347
+ kind: "retype",
348
+ origin: "physics",
349
+ name: "trigger_event",
350
+ fields: ["a", "b"],
351
+ tsType: "{ id: Hash; group: Hash }",
352
+ upstream: ["table"],
353
+ evidence: `gamesys/src/gamesys/scripts/script_physics.cpp:PushTrigger sets these two fields on each body, and ${physicsBody("Trigger")}`,
354
+ },
355
+ ];
356
+
357
+ export function applyMessageEngineCorrections(
358
+ catalog: MessageCatalog,
359
+ overlay: readonly MessageEngineCorrection[] = MESSAGE_ENGINE_CORRECTIONS,
360
+ ): MessageCatalog {
361
+ const entries = catalog.entries.map((entry) => ({
362
+ ...entry,
363
+ payload: entry.payload.map((field) => ({ ...field })),
364
+ }));
365
+ for (const correction of overlay) {
366
+ const identity = `(origin=${correction.origin}, name=${correction.name})`;
367
+ const stale = (fact: string) =>
368
+ new Error(
369
+ `applyMessageEngineCorrections: the catalog ${fact} for ${identity}; delete the correction`,
370
+ );
371
+ const entry = entries.find(
372
+ (candidate) => candidate.origin === correction.origin && candidate.name === correction.name,
373
+ );
374
+ if (correction.kind === "add-message") {
375
+ if (entry) throw stale("already declares message");
376
+ entries.push({
377
+ name: correction.name,
378
+ origin: correction.origin,
379
+ description: correction.description,
380
+ payload: correction.fields.map((field) => ({ ...field, types: [...field.types] })),
381
+ });
382
+ continue;
383
+ }
384
+ if (!entry) {
385
+ throw new Error(`applyMessageEngineCorrections: no catalog message matches ${identity}`);
386
+ }
387
+ if (correction.kind === "add-field") {
388
+ for (const field of correction.fields) {
389
+ if (entry.payload.some((candidate) => candidate.name === field.name)) {
390
+ throw stale(`already declares field ${field.name}`);
391
+ }
392
+ entry.payload.push({ ...field, types: [...field.types] });
393
+ }
394
+ continue;
395
+ }
396
+ for (const name of correction.fields) {
397
+ const field = entry.payload.find((candidate) => candidate.name === name);
398
+ if (!field) {
399
+ throw new Error(
400
+ `applyMessageEngineCorrections: no catalog field matches ${identity} field=${name}`,
401
+ );
402
+ }
403
+ if (correction.kind === "retype") {
404
+ if (field.types.join("|") !== correction.upstream.join("|")) {
405
+ throw new Error(
406
+ `applyMessageEngineCorrections: the catalog now declares ${field.types.join("|")} for field ${name} of ${identity}, not the pinned ${correction.upstream.join("|")}; re-verify the correction`,
407
+ );
408
+ }
409
+ field.types = [correction.tsType];
410
+ continue;
411
+ }
412
+ const optional = correction.kind === "optional";
413
+ if (field.optional === optional) throw stale(`field ${name} is already ${correction.kind}`);
414
+ field.optional = optional;
415
+ }
416
+ }
417
+ return { entries };
418
+ }
419
+
420
+ // The built-in message surface is the catalog plus these overlays. Every reader
421
+ // of it goes through here, so a message the engine overlay adds is never missing
422
+ // from one of them.
423
+ export function applyMessageOverlays(catalog: MessageCatalog): MessageCatalog {
424
+ return applyMessageEngineCorrections(
425
+ applyMessageFieldCorrections(applyMessageDeprecations(catalog)),
426
+ );
427
+ }
428
+
140
429
  const TS_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
141
430
  const ENGINE_TYPES = [
142
431
  "Hash",
@@ -36,7 +36,7 @@ declare global {
36
36
  type Opaque<Name extends string> = Core.Opaque<Name>;
37
37
  /** A message-passing address with `socket`, `path`, and `fragment`; see {@link Core.Url}. */
38
38
  type Url = Core.Url;
39
- /** A read-only numeric vector accessed by index; see {@link Core.Vector}. */
39
+ /** A numeric vector read and written by 1-based index; see {@link Core.Vector}. */
40
40
  type Vector = Core.Vector;
41
41
  /** A three-component vector with `x`, `y`, `z`; see {@link Core.Vector3}. */
42
42
  type Vector3 = Core.Vector3;
@@ -46,4 +46,6 @@ declare global {
46
46
  type Quaternion = Core.Quaternion;
47
47
  /** A 4x4 transformation matrix; see {@link Core.Matrix4}. */
48
48
  type Matrix4 = Core.Matrix4;
49
+ /** An array constant read back from a constant buffer, 1-based with no length; see {@link Core.ConstantArray}. */
50
+ type ConstantArray<T> = Core.ConstantArray<T>;
49
51
  }
@@ -1,6 +1,6 @@
1
1
  /** @noSelfInFile */
2
2
 
3
- import type { Hash, Quaternion, Url, Vector3, Vector4 } from "./core-types";
3
+ import type { Hash, Matrix4, Quaternion, Url, Vector3, Vector4 } from "./core-types";
4
4
 
5
5
  interface ScriptProperty<TValue> {
6
6
  readonly __defoldScriptProperty: TValue;
@@ -61,7 +61,7 @@ declare global {
61
61
  url: SceneAddress | Hash | Url,
62
62
  property: string | Hash,
63
63
  options?: GoPropertyOptions,
64
- ): number | boolean | string | Hash | Url | Vector3 | Vector4 | Quaternion;
64
+ ): number | boolean | string | Hash | Url | Vector3 | Vector4 | Quaternion | Matrix4;
65
65
  /**
66
66
  * sets a named property of the specified game object or component, or a material constant
67
67
  *
@@ -97,7 +97,17 @@ declare global {
97
97
  function set(
98
98
  url: SceneAddress | Hash | Url,
99
99
  property: string | Hash,
100
- value: number | boolean | string | Hash | Url | Vector3 | Vector4 | Quaternion | Vector4[],
100
+ value:
101
+ | number
102
+ | boolean
103
+ | string
104
+ | Hash
105
+ | Url
106
+ | Vector3
107
+ | Vector4
108
+ | Quaternion
109
+ | Matrix4
110
+ | Vector4[],
101
111
  options?: GoPropertyOptions,
102
112
  ): void;
103
113
  /**
package/src/lifecycle.ts CHANGED
@@ -7,51 +7,51 @@ export interface InputTouch {
7
7
  /**
8
8
  * Identifier for the touch during its lifetime.
9
9
  */
10
- id?: number;
10
+ id: number;
11
11
  /**
12
12
  * Whether the finger was pressed this frame.
13
13
  */
14
- pressed?: boolean;
14
+ pressed: boolean;
15
15
  /**
16
16
  * Whether the finger was released this frame.
17
17
  */
18
- released?: boolean;
18
+ released: boolean;
19
19
  /**
20
20
  * Number of taps, such as one for a single tap and two for a double tap.
21
21
  */
22
- tap_count?: number;
22
+ tap_count: number;
23
23
  /**
24
24
  * Touch x-coordinate.
25
25
  */
26
- x?: number;
26
+ x: number;
27
27
  /**
28
28
  * Touch y-coordinate.
29
29
  */
30
- y?: number;
30
+ y: number;
31
31
  /**
32
32
  * Change in the touch x-coordinate.
33
33
  */
34
- dx?: number;
34
+ dx: number;
35
35
  /**
36
36
  * Change in the touch y-coordinate.
37
37
  */
38
- dy?: number;
38
+ dy: number;
39
39
  /**
40
40
  * Touch x-coordinate in screen space.
41
41
  */
42
- screen_x?: number;
42
+ screen_x: number;
43
43
  /**
44
44
  * Touch y-coordinate in screen space.
45
45
  */
46
- screen_y?: number;
46
+ screen_y: number;
47
47
  /**
48
48
  * Change in the touch x-coordinate in screen space.
49
49
  */
50
- screen_dx?: number;
50
+ screen_dx: number;
51
51
  /**
52
52
  * Change in the touch y-coordinate in screen space.
53
53
  */
54
- screen_dy?: number;
54
+ screen_dy: number;
55
55
  }
56
56
 
57
57
  // Fields and docs follow the go ref-doc's `on_input.action` STRUCT, whose keys are
@@ -363,7 +363,7 @@ export type GuiScriptHooks<TSelf, TInitState = TSelf> = Omit<
363
363
 
364
364
  export type RenderScriptHooks<TSelf, TInitState = TSelf> = Omit<
365
365
  ScriptHooks<TSelf, TInitState>,
366
- "on_input"
366
+ "on_input" | "final" | "fixed_update" | "late_update"
367
367
  >;
368
368
 
369
369
  // The factory hook table plus the value-keyed `properties` field. `TProps` is
@@ -547,9 +547,9 @@ export function defineGuiScript<
547
547
  * {@link defineScript}, and is erased by the transpiler's `lifecycle-erasure`
548
548
  * pass into the flat Defold chunk shape.
549
549
  *
550
- * `RenderScriptHooks` is `Omit<ScriptHooks, "on_input">` — render scripts do not
551
- * receive input. It accepts the rest of the set, all optional: `init`,
552
- * `update`, `fixed_update`, `late_update`, `on_message`, `final`, `on_reload`.
550
+ * `RenderScriptHooks` is `Omit<ScriptHooks, "on_input" | "final" |
551
+ * "fixed_update" | "late_update">` — the engine calls only four render-script
552
+ * hooks, all optional: `init`, `update`, `on_message`, `on_reload`.
553
553
  *
554
554
  * Scaffold it with the `def-ts-defineRenderScript-inferred-self` / `def-ts-defineRenderScript-typed-self` VSCode snippets
555
555
  * from `defold-typescript init`.
@@ -334,6 +334,11 @@ export function mapLualsExpression(
334
334
  return { ts: mapToken(token, walk), unknowns: walk.unknowns };
335
335
  }
336
336
 
337
+ /** Whether a raw token is a typed LuaLS `fun(...)` expression rather than a bare `function(...)`. */
338
+ export function isLualsFunctionToken(token: string): boolean {
339
+ return /^fun\s*\(/.test(token.trim());
340
+ }
341
+
337
342
  /**
338
343
  * Map a `fun(...)` expression to a TypeScript **call signature** — the colon-return
339
344
  * form `(params): ret` an interface uses to become callable, not the `=>` arrow a
@@ -345,7 +350,7 @@ export function mapLualsCallSignatureExpression(
345
350
  position: LualsPosition = "output",
346
351
  ): LualsMapResult {
347
352
  const trimmed = token.trim();
348
- if (!/^fun\s*\(/.test(trimmed)) {
353
+ if (!isLualsFunctionToken(trimmed)) {
349
354
  throw new Error(`mapLualsCallSignature: expected a "fun(...)" token, got "${token}".`);
350
355
  }
351
356
  const walk: Walk = { resolveLeaf, unknowns: [], position };
@@ -0,0 +1,39 @@
1
+ /** @noSelfInFile */
2
+ import type { Hash } from "./core-types";
3
+
4
+ // `physics.set_event_listener` sets `type` on every event to its message-name
5
+ // hash (script_physics.cpp), but a `Hash` is an object type, not a unit type, so
6
+ // an `event.type == hash("…")` check cannot narrow the event union. This guard
7
+ // re-introduces the literal at the use site. The transpiler lowers the call to
8
+ // `event.type == hash("…")` (physics-event-guard-lowering.ts), so this package
9
+ // emits no runtime Lua. It names no `physics` type, so it compiles on a surface
10
+ // whose reference declares no event union.
11
+ type PhysicsEventName<E> = E extends { type: Hash<infer N> } ? N : never;
12
+
13
+ declare global {
14
+ /**
15
+ * Type guard for a `physics.set_event_listener` callback: narrows one event
16
+ * to the member of the event union whose `type` is the hash of `name`. The
17
+ * engine sets `type` on every event, but a `Hash` cannot act as a union
18
+ * discriminant, so an `event.type == hash("collision_event")` check leaves the
19
+ * event unnarrowed — this guard re-introduces the literal.
20
+ *
21
+ * @param event - one entry of the `events` array the listener received.
22
+ * @param name - the event's message name (e.g. `"collision_event"`).
23
+ * @returns `true` when `event.type` is `hash(name)`, narrowing `event` to that event.
24
+ * @example
25
+ * ```ts
26
+ * physics.set_event_listener((self, events) => {
27
+ * for (const event of events) {
28
+ * if (isPhysicsEvent(event, "collision_event")) {
29
+ * print(event.a.id, event.b.id);
30
+ * }
31
+ * }
32
+ * });
33
+ * ```
34
+ */
35
+ function isPhysicsEvent<E extends { type: Hash }, K extends PhysicsEventName<E>>(
36
+ event: E,
37
+ name: K,
38
+ ): event is Extract<E, { type: Hash<K> }>;
39
+ }
@@ -12,7 +12,7 @@ declare global {
12
12
  * @param parameters - table of buffer parameters, see the description for available keys and values
13
13
  * @returns new render target
14
14
  */
15
- function render_target(parameters: Record<string | number, unknown>): Opaque<"render_target">;
15
+ function render_target(parameters: render.RenderTargetParams): Opaque<"render_target">;
16
16
  /**
17
17
  * Creates a new named render target according to the supplied specification
18
18
  * table. The name is used by the profiler and by render target lookups.
@@ -23,7 +23,7 @@ declare global {
23
23
  */
24
24
  function render_target(
25
25
  name: string,
26
- parameters: Record<string | number, unknown>,
26
+ parameters: render.RenderTargetParams,
27
27
  ): Opaque<"render_target">;
28
28
  }
29
29
  }
@@ -248,6 +248,10 @@ export interface ReturnTypeProvenance {
248
248
  readonly sightedIn: readonly string[];
249
249
  readonly neededBy: readonly string[];
250
250
  readonly resolvedIn: readonly string[];
251
+ // Targets whose vendored binding does not show a binding-pinned entry's pin,
252
+ // so the emitter leaves the entry off there. Never a resolution: upstream
253
+ // documented nothing, the engine source changed.
254
+ readonly unboundIn: readonly string[];
251
255
  readonly driftedIn: readonly string[];
252
256
  readonly typeDrift: readonly string[];
253
257
  }
@@ -255,14 +259,26 @@ export interface ReturnTypeProvenance {
255
259
  // Same ranking argument as `FIELD_VERDICT_RANK`: `emitReturn` applies the
256
260
  // correction to every declaration of the FQN in a target, so one drifted
257
261
  // declaration makes that target's emit wrong whatever its siblings say.
258
- type ReturnVerdict = "drift" | "needed" | "resolved";
262
+ // Binding evidence is per target, so for a binding-pinned entry `unbound` never
263
+ // meets another verdict in the same target.
264
+ type ReturnVerdict = "drift" | "needed" | "unbound" | "resolved";
259
265
 
260
266
  const RETURN_VERDICT_RANK: Record<ReturnVerdict, number> = {
261
- drift: 2,
262
- needed: 1,
267
+ drift: 3,
268
+ needed: 2,
269
+ unbound: 1,
263
270
  resolved: 0,
264
271
  };
265
272
 
273
+ // Whether the declared tokens already say what the correction does. Tokens can
274
+ // state only the nil: a binding-pinned `ts` entry corrects what the binding sets
275
+ // (a field it never pushes), so upstream fixing that shows as a retype, which is
276
+ // drift, never as a resolution.
277
+ function declaresCorrection(tokens: readonly string[], correction: ReturnTypeCorrection): boolean {
278
+ if (correction.nullable === undefined && correction.binding !== undefined) return false;
279
+ return correction.nullable === false ? !tokens.includes("nil") : tokens.includes("nil");
280
+ }
281
+
266
282
  // The return-type class has the same two-sided evidence: the declared tokens
267
283
  // still say what the correction contradicts, and the slot prose still states the
268
284
  // contradiction. A target that fixes either half no longer needs the entry.
@@ -273,9 +289,16 @@ const RETURN_VERDICT_RANK: Record<ReturnVerdict, number> = {
273
289
  // return correction replaces the whole emitted type, so a base upstream no longer
274
290
  // declares means the correction now overwrites a type nobody verified — a
275
291
  // decision for a human, not a deletion and not a silent bank.
292
+ //
293
+ // A binding-pinned entry rests on the engine source rather than the prose, so
294
+ // its prose is never searched. `bindingEvidence` maps each vendored target to
295
+ // the keys whose pin holds there; a target absent from it shows no evidence.
296
+ // A target without the entry's evidence is `unbound` before its tokens are
297
+ // read, because the emitter leaves the entry off there.
276
298
  export function returnCorrectionProvenance(
277
299
  entries: readonly [string, ReturnTypeCorrection][],
278
300
  surfaces: readonly ProvenanceSurface[],
301
+ bindingEvidence: ReadonlyMap<string, ReadonlySet<string>> = new Map(),
279
302
  ): ReturnTypeProvenance[] {
280
303
  return entries.map(([key, correction]) => {
281
304
  const namespace = namespaceOf(key);
@@ -291,7 +314,12 @@ export function returnCorrectionProvenance(
291
314
  const tokens = slot.types ?? [];
292
315
 
293
316
  let verdict: ReturnVerdict;
294
- if (!pinMatches(tokens, pins)) {
317
+ if (
318
+ correction.binding !== undefined &&
319
+ bindingEvidence.get(surface.target)?.has(key) !== true
320
+ ) {
321
+ verdict = "unbound";
322
+ } else if (!pinMatches(tokens, pins)) {
295
323
  verdict = "drift";
296
324
  const messages = drift.get(surface.target) ?? new Set<string>();
297
325
  drift.set(surface.target, messages);
@@ -299,8 +327,9 @@ export function returnCorrectionProvenance(
299
327
  `${surface.target}: pinned ${pins.map((pin) => pin.join("|")).join(" or ")}, found ${tokens.join("|")}`,
300
328
  );
301
329
  } else if (
302
- tokens.includes("nil") ||
303
- !decodeSlotProse(slot.doc ?? "").includes(correction.reason)
330
+ declaresCorrection(tokens, correction) ||
331
+ (correction.binding === undefined &&
332
+ !decodeSlotProse(slot.doc ?? "").includes(correction.reason))
304
333
  ) {
305
334
  verdict = "resolved";
306
335
  } else {
@@ -323,6 +352,7 @@ export function returnCorrectionProvenance(
323
352
  sightedIn,
324
353
  neededBy: inBucket("needed"),
325
354
  resolvedIn: inBucket("resolved"),
355
+ unboundIn: inBucket("unbound"),
326
356
  driftedIn,
327
357
  typeDrift: driftedIn.flatMap((target) => [...(drift.get(target) ?? [])]).sort(),
328
358
  };
@@ -4,7 +4,8 @@
4
4
  // params, and the `WINDOW_EVENT_*` constants are branded numbers — so TS can
5
5
  // neither correlate the two params nor use a branded number as a discriminant.
6
6
  // This guard re-introduces the discriminant at the use site, narrowing the
7
- // untyped `data` payload (only `WINDOW_EVENT_RESIZED` carries fields). It mirrors
7
+ // untyped `data` payload (only `WINDOW_EVENT_RESIZED` carries fields; every
8
+ // other event gets an empty table, script_window.cpp). It mirrors
8
9
  // `isMessage` for `on_message`; the transpiler lowers the call to a bare
9
10
  // `event == window.WINDOW_EVENT_*` (window-event-guard-lowering.ts), so this
10
11
  // package emits no runtime Lua. The event kinds are read off the loaded
@@ -14,7 +15,7 @@ type WindowEventKind = (typeof window)[Extract<keyof typeof window, `WINDOW_EVEN
14
15
 
15
16
  type WindowEventData<K extends WindowEventKind> = K extends typeof window.WINDOW_EVENT_RESIZED
16
17
  ? { width: number; height: number }
17
- : undefined;
18
+ : Record<string, never>;
18
19
 
19
20
  declare global {
20
21
  /**
@@ -24,7 +25,8 @@ declare global {
24
25
  * params and the constants are branded numbers, so TS cannot auto-narrow `data`
25
26
  * from an `event === window.WINDOW_EVENT_RESIZED` check — this guard
26
27
  * re-introduces the discriminant. Only `WINDOW_EVENT_RESIZED` carries fields
27
- * (`{ width, height }`); every other event narrows `data` to `undefined`.
28
+ * (`{ width, height }`); every other event receives an empty table, so `data`
29
+ * narrows to `Record<string, never>`.
28
30
  *
29
31
  * @param event - the event constant the callback received.
30
32
  * @param data - the untyped data payload the callback received.