@defold-typescript/types 0.19.3 → 0.20.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 (86) hide show
  1. package/api-availability.json +2755 -0
  2. package/api-migrations.json +31 -0
  3. package/api-signatures.json +2324 -0
  4. package/api-targets.json +493 -45
  5. package/generated/b2d.d.ts +6 -0
  6. package/generated/b2d_body.d.ts +329 -1
  7. package/generated/b2d_chain.d.ts +106 -0
  8. package/generated/b2d_fixture.d.ts +155 -0
  9. package/generated/b2d_joint.d.ts +823 -0
  10. package/generated/b2d_shape.d.ts +215 -0
  11. package/generated/b2d_world.d.ts +314 -0
  12. package/generated/builtin-messages.d.ts +18 -0
  13. package/generated/camera.d.ts +62 -5
  14. package/generated/compute.d.ts +306 -0
  15. package/generated/go.d.ts +29 -4
  16. package/generated/graphics.d.ts +95 -0
  17. package/generated/gui.d.ts +1 -3
  18. package/generated/json.d.ts +2 -2
  19. package/generated/kinds/gui-script.d.ts +7 -0
  20. package/generated/kinds/render-script.d.ts +7 -0
  21. package/generated/kinds/script.d.ts +7 -0
  22. package/generated/label.d.ts +1 -1
  23. package/generated/liveupdate.d.ts +22 -4
  24. package/generated/material.d.ts +444 -0
  25. package/generated/model.d.ts +46 -5
  26. package/generated/physics.d.ts +1 -3
  27. package/generated/profiler.d.ts +2 -3
  28. package/generated/render.d.ts +25 -9
  29. package/generated/resource.d.ts +2 -2
  30. package/generated/sprite.d.ts +15 -1
  31. package/generated/tilemap.d.ts +14 -0
  32. package/generated/versions/defold-1.12.4/b2d.d.ts +27 -0
  33. package/generated/versions/defold-1.12.4/b2d_body.d.ts +348 -0
  34. package/generated/versions/defold-1.12.4/buffer.d.ts +176 -0
  35. package/generated/versions/defold-1.12.4/camera.d.ts +245 -0
  36. package/generated/versions/defold-1.12.4/collectionfactory.d.ts +146 -0
  37. package/generated/versions/defold-1.12.4/collectionproxy.d.ts +71 -0
  38. package/generated/versions/defold-1.12.4/crash.d.ts +143 -0
  39. package/generated/versions/defold-1.12.4/factory.d.ts +123 -0
  40. package/generated/versions/defold-1.12.4/font.d.ts +81 -0
  41. package/generated/versions/defold-1.12.4/go.d.ts +1029 -0
  42. package/generated/versions/defold-1.12.4/graphics.d.ts +226 -0
  43. package/generated/versions/defold-1.12.4/gui.d.ts +2372 -0
  44. package/generated/versions/defold-1.12.4/html5.d.ts +53 -0
  45. package/generated/versions/defold-1.12.4/http.d.ts +84 -0
  46. package/generated/versions/defold-1.12.4/iac.d.ts +18 -0
  47. package/generated/versions/defold-1.12.4/iap.d.ts +60 -0
  48. package/generated/versions/defold-1.12.4/image.d.ts +139 -0
  49. package/generated/versions/defold-1.12.4/index.d.ts +41 -0
  50. package/generated/versions/defold-1.12.4/json.d.ts +76 -0
  51. package/generated/versions/defold-1.12.4/label.d.ts +94 -0
  52. package/generated/versions/defold-1.12.4/liveupdate.d.ts +122 -0
  53. package/generated/versions/defold-1.12.4/model.d.ts +205 -0
  54. package/generated/versions/defold-1.12.4/msg.d.ts +10 -0
  55. package/generated/versions/defold-1.12.4/particlefx.d.ts +166 -0
  56. package/generated/versions/defold-1.12.4/physics.d.ts +547 -0
  57. package/generated/versions/defold-1.12.4/profiler.d.ts +234 -0
  58. package/generated/versions/defold-1.12.4/push.d.ts +66 -0
  59. package/generated/versions/defold-1.12.4/render.d.ts +1149 -0
  60. package/generated/versions/defold-1.12.4/resource.d.ts +1518 -0
  61. package/generated/versions/defold-1.12.4/socket.d.ts +795 -0
  62. package/generated/versions/defold-1.12.4/sound.d.ts +291 -0
  63. package/generated/versions/defold-1.12.4/sprite.d.ts +143 -0
  64. package/generated/versions/defold-1.12.4/sys.d.ts +695 -0
  65. package/generated/versions/defold-1.12.4/tilemap.d.ts +181 -0
  66. package/generated/versions/defold-1.12.4/timer.d.ts +114 -0
  67. package/generated/versions/defold-1.12.4/types.d.ts +61 -0
  68. package/generated/versions/defold-1.12.4/vmath.d.ts +806 -0
  69. package/generated/versions/defold-1.12.4/webview.d.ts +68 -0
  70. package/generated/versions/defold-1.12.4/window.d.ts +198 -0
  71. package/generated/versions/defold-1.12.4/zlib.d.ts +40 -0
  72. package/index.d.ts +22 -0
  73. package/package.json +8 -1
  74. package/scripts/fidelity-audit.ts +69 -12
  75. package/scripts/fidelity-baseline.json +56 -0
  76. package/scripts/generate-api-availability.ts +139 -0
  77. package/scripts/generate-api-signatures.ts +66 -0
  78. package/scripts/import-defold-release.ts +480 -0
  79. package/scripts/regen.ts +53 -6
  80. package/scripts/sync-api-docs.ts +89 -41
  81. package/src/api-availability.ts +0 -0
  82. package/src/core-types.ts +9 -0
  83. package/src/emit-dts.ts +674 -17
  84. package/src/emit-messages.ts +72 -2
  85. package/src/index.ts +26 -1
  86. package/src/lifecycle.ts +43 -2
package/src/emit-dts.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type ApiSymbolIdentity, normalizedFunctionSignature } from "./api-availability";
1
2
  import type {
2
3
  ApiConstant,
3
4
  ApiFunction,
@@ -150,6 +151,28 @@ export const ARBITRARY_TABLE_SLOTS = new Set([
150
151
  "render.set_render_target",
151
152
  ]);
152
153
 
154
+ // Per-slot companion to ARBITRARY_TABLE_SLOTS (`element:kind:name`), for a
155
+ // genuinely-undocumented table slot on an element whose *other* slots are
156
+ // documented — an element-level entry would wrongly suppress those. Each slot's
157
+ // doc names no fields (Box2D internal/opaque structs), so `Record` is faithful,
158
+ // not a loss. The audit skips counting these; the emitter already leaves an
159
+ // uncurated slot as `Record`. Add deliberately, one rationale per line.
160
+ export const ARBITRARY_TABLE_SLOT_KEYS = new Set([
161
+ // "the chain definition" / "the shape definition" / "explosion definition" —
162
+ // create-side option tables with no documented field list at this slot.
163
+ "b2d.body.create_chain:param:definition",
164
+ "b2d.body.create_shape:param:definition",
165
+ "b2d.world.explode:param:definition",
166
+ "b2d.joint.create_filter:param:definition", // "optional definition table"
167
+ // opaque Box2D result structs: the doc names no fields.
168
+ "b2d.chain.get_geometry:return:geometry", // "chain geometry table"
169
+ "b2d.body.get_contact_data:return:contacts", // "array of contact tables"
170
+ "b2d.shape.get_contact_data:return:contacts", // "array of contact tables"
171
+ "b2d.world.collide_mover:return:planes", // "array of plane result tables"
172
+ "b2d.world.get_profile:return:profile", // "world profiling data"
173
+ "b2d.world.get_counters:return:counters", // "world counters"
174
+ ]);
175
+
153
176
  // skipFunction FQNs (see `api-targets.json`'s `skipFunctions`) whose absence
154
177
  // from the generated surface is *not* a fidelity loss — each is replaced by a
155
178
  // hand-written, better-typed overload in the cited `*-overloads.d.ts`, so the
@@ -256,10 +279,99 @@ export type TableSlotCuration =
256
279
  | { kind: "mapping"; key: string; value: string | readonly TableField[] | NestedMapping }
257
280
  | { kind: "array"; element: string | readonly string[] }
258
281
  | { kind: "object"; fields: readonly TableField[] }
259
- | { kind: "array-object"; fields: readonly TableField[] };
282
+ | { kind: "array-object"; fields: readonly TableField[] }
283
+ // A `<key> -> <args table>` slot: the doc's `<dl>` describes the *value* shape,
284
+ // keyed by an arbitrary name (constant/sampler/attribute). The parser recovers
285
+ // the inner args fields; emit wraps them as `Record<string, { <fields> }>`,
286
+ // restoring the keyed-by-name layer the flat object curation drops.
287
+ | { kind: "keyed-object" };
260
288
 
261
289
  export const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
262
290
 
291
+ // Shared Box2D (b2d.*) table shapes, curated from the ref-doc prose (the docs
292
+ // name fields in running text, not a machine-readable `<dl>`). Reused across the
293
+ // b2d submodule curations below so a single shape is defined once.
294
+ //
295
+ // The shape table is a discriminated union of circle/edge/polygon/box/chain
296
+ // forms keyed by `type` (a b2d.shape.SHAPE_TYPE_* constant); every kind-specific
297
+ // field is optional because only one form's fields are present per value.
298
+ const B2D_SHAPE_TABLE_FIELDS: readonly TableField[] = [
299
+ { name: "type", types: ["number"] },
300
+ { name: "radius", types: ["number"], optional: true },
301
+ { name: "center", types: ["vector3"], optional: true },
302
+ { name: "v0", types: ["vector3"], optional: true },
303
+ { name: "v1", types: ["vector3"], optional: true },
304
+ { name: "v2", types: ["vector3"], optional: true },
305
+ { name: "v3", types: ["vector3"], optional: true },
306
+ { name: "vertices", types: ["table"], tsType: "Vector3[]", optional: true },
307
+ { name: "hx", types: ["number"], optional: true },
308
+ { name: "hy", types: ["number"], optional: true },
309
+ { name: "angle", types: ["number"], optional: true },
310
+ { name: "loop", types: ["boolean"], optional: true },
311
+ { name: "prev_vertex", types: ["vector3"], optional: true },
312
+ { name: "next_vertex", types: ["vector3"], optional: true },
313
+ ];
314
+ // A fixture info entry (index/type/sensor/density/friction/restitution/child_count).
315
+ const B2D_FIXTURE_INFO_FIELDS: readonly TableField[] = [
316
+ { name: "index", types: ["number"] },
317
+ { name: "type", types: ["number"] },
318
+ { name: "sensor", types: ["boolean"] },
319
+ { name: "density", types: ["number"] },
320
+ { name: "friction", types: ["number"] },
321
+ { name: "restitution", types: ["number"] },
322
+ { name: "child_count", types: ["number"] },
323
+ ];
324
+ // A shape info entry: only `shape_id` is documented ("Each entry includes
325
+ // shape_id"); other members are engine-internal and left untyped.
326
+ const B2D_SHAPE_INFO_FIELDS: readonly TableField[] = [{ name: "shape_id", types: ["number"] }];
327
+ // mass properties: mass/inertia scalars plus the local-space center point.
328
+ const B2D_MASS_DATA_FIELDS: readonly TableField[] = [
329
+ { name: "mass", types: ["number"] },
330
+ { name: "center", types: ["vector3"] },
331
+ { name: "inertia", types: ["number"] },
332
+ ];
333
+ // an AABB corner pair.
334
+ const B2D_AABB_FIELDS: readonly TableField[] = [
335
+ { name: "lower", types: ["vector3"] },
336
+ { name: "upper", types: ["vector3"] },
337
+ ];
338
+ // a broad-phase query filter. group_index is optional on the query side.
339
+ const B2D_QUERY_FILTER_FIELDS: readonly TableField[] = [
340
+ { name: "category_bits", types: ["number"] },
341
+ { name: "mask_bits", types: ["number"] },
342
+ { name: "group_index", types: ["number"], optional: true },
343
+ ];
344
+ // the fixture filter data: category/mask bits plus a group index (all documented).
345
+ const B2D_FILTER_DATA_FIELDS: readonly TableField[] = [
346
+ { name: "category_bits", types: ["number"] },
347
+ { name: "mask_bits", types: ["number"] },
348
+ { name: "group_index", types: ["number"] },
349
+ ];
350
+ // the mover filter: category/mask bits only (no group index).
351
+ const B2D_MOVER_FILTER_FIELDS: readonly TableField[] = [
352
+ { name: "category_bits", types: ["number"] },
353
+ { name: "mask_bits", types: ["number"] },
354
+ ];
355
+ // tree traversal stats reported by a broad-phase query.
356
+ const B2D_QUERY_STATS_FIELDS: readonly TableField[] = [
357
+ { name: "node_visits", types: ["number"] },
358
+ { name: "leaf_visits", types: ["number"] },
359
+ ];
360
+ // a mover capsule.
361
+ const B2D_CAPSULE_FIELDS: readonly TableField[] = [
362
+ { name: "center1", types: ["vector3"] },
363
+ { name: "center2", types: ["vector3"] },
364
+ { name: "radius", types: ["number"] },
365
+ ];
366
+ // a raycast/shapecast hit entry.
367
+ const B2D_CAST_HIT_FIELDS: readonly TableField[] = [
368
+ { name: "fixture", types: ["number"] },
369
+ { name: "shape", types: ["number"] },
370
+ { name: "point", types: ["vector3"] },
371
+ { name: "normal", types: ["vector3"] },
372
+ { name: "fraction", types: ["number"] },
373
+ ];
374
+
263
375
  export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new Map([
264
376
  ["collectionfactory.create:return:ids", { kind: "mapping", key: "hash", value: "hash" }],
265
377
  // font.get_info's `info` return is a `<dl>` with `path: hash` and a nested
@@ -560,6 +672,359 @@ export const TABLE_SLOT_CURATIONS: ReadonlyMap<string, TableSlotCuration> = new
560
672
  ],
561
673
  },
562
674
  ],
675
+ // compute/material shader-constant, sampler, and vertex-attribute setters take
676
+ // "a table keyed by <name> with args tables as values": the `<dl>` documents
677
+ // the *value* shape, keyed by an arbitrary constant/sampler/attribute name.
678
+ // The keyed-object curation re-keys the parser-recovered args fields as
679
+ // `Record<string, { … }>`, restoring the keyed-by-name layer a flat object
680
+ // curation drops — so the documented `{ tint: { value: … } }` call compiles.
681
+ ["compute.set_constants:param:constants", { kind: "keyed-object" }],
682
+ ["compute.set_samplers:param:samplers", { kind: "keyed-object" }],
683
+ ["material.set_constants:param:constants", { kind: "keyed-object" }],
684
+ ["material.set_samplers:param:samplers", { kind: "keyed-object" }],
685
+ ["material.set_vertex_attributes:param:attributes", { kind: "keyed-object" }],
686
+ // compute/material texture setters take "a table keyed by sampler name with
687
+ // texture resources as values" — a name-keyed map to a resource-path hash.
688
+ ["compute.set_textures:param:textures", { kind: "mapping", key: "string", value: "hash" }],
689
+ ["material.set_textures:param:textures", { kind: "mapping", key: "string", value: "hash" }],
690
+ // model blend weights are a numeric Lua array ("array of weight values"), not
691
+ // an opaque record; get returns the array, set takes it (optional on the param
692
+ // side — omitting it resets the weights).
693
+ ["model.get_blend_weights:return:weights", { kind: "array", element: "number" }],
694
+ ["model.set_blend_weights:param:weights", { kind: "array", element: "number" }],
695
+ // graphics adapter inspection. get_engine_adapters returns "array of adapter
696
+ // family name strings"; get_adapter_info returns a documented object whose
697
+ // `limits` sub-table is a flat number map, `extensions` a string array, and
698
+ // `features` an array of graphics.CONTEXT_FEATURE_* ids (numbers). The
699
+ // `limits`/`extensions`/`features` shapes live in codehilite blocks the parser
700
+ // cannot read, so the whole object is curated from the prose field list.
701
+ ["graphics.get_engine_adapters:return:adapters", { kind: "array", element: "string" }],
702
+ [
703
+ "graphics.get_adapter_info:return:info",
704
+ {
705
+ kind: "object",
706
+ fields: [
707
+ { name: "family", types: ["string"] },
708
+ { name: "version_major", types: ["number"] },
709
+ { name: "version_minor", types: ["number"] },
710
+ {
711
+ name: "limits",
712
+ types: ["table"],
713
+ fields: [
714
+ { name: "max_texture_size_2d", types: ["number"] },
715
+ { name: "max_texture_size_3d", types: ["number"] },
716
+ { name: "max_texture_size_cube", types: ["number"] },
717
+ { name: "max_texture_array_layers", types: ["number"] },
718
+ { name: "max_framebuffer_width", types: ["number"] },
719
+ { name: "max_framebuffer_height", types: ["number"] },
720
+ { name: "max_color_attachments", types: ["number"] },
721
+ { name: "max_samplers_per_stage", types: ["number"] },
722
+ { name: "max_textures_per_stage", types: ["number"] },
723
+ { name: "max_vertex_attributes", types: ["number"] },
724
+ { name: "max_vertex_buffers", types: ["number"] },
725
+ { name: "max_compute_workgroup_size_x", types: ["number"] },
726
+ { name: "max_compute_workgroup_size_y", types: ["number"] },
727
+ { name: "max_compute_workgroup_size_z", types: ["number"] },
728
+ { name: "max_compute_workgroup_invocations", types: ["number"] },
729
+ { name: "max_compute_shared_memory_size", types: ["number"] },
730
+ { name: "max_uniform_buffer_range", types: ["number"] },
731
+ { name: "max_storage_buffer_range", types: ["number"] },
732
+ ],
733
+ },
734
+ { name: "extensions", types: ["table"], tsType: "string[]" },
735
+ { name: "features", types: ["table"], tsType: "number[]" },
736
+ ],
737
+ },
738
+ ],
739
+ // ---- Box2D (b2d.*) curated table shapes ----------------------------------
740
+ // All field names come from the ref-doc prose; genuinely-undocumented slots
741
+ // (chain/shape/explosion definitions, contact/plane/profile/counter structs)
742
+ // are per-slot-arbitrary via ARBITRARY_TABLE_SLOT_KEYS, not curated here.
743
+ //
744
+ // b2d root: version info table.
745
+ [
746
+ "b2d.get_version:return:info",
747
+ {
748
+ kind: "object",
749
+ fields: [
750
+ { name: "version", types: ["number"] },
751
+ { name: "major", types: ["number"] },
752
+ { name: "middle", types: ["number"] },
753
+ { name: "minor", types: ["number"] },
754
+ ],
755
+ },
756
+ ],
757
+ // b2d.body
758
+ ["b2d.body.compute_aabb:return:aabb", { kind: "object", fields: B2D_AABB_FIELDS }],
759
+ [
760
+ "b2d.body.create_fixture:param:definition",
761
+ {
762
+ kind: "object",
763
+ fields: [
764
+ { name: "shape", types: ["table"], fields: [...B2D_SHAPE_TABLE_FIELDS] },
765
+ { name: "friction", types: ["number"] },
766
+ { name: "restitution", types: ["number"] },
767
+ { name: "density", types: ["number"] },
768
+ { name: "sensor", types: ["boolean"] },
769
+ { name: "filter", types: ["table"], fields: [...B2D_FILTER_DATA_FIELDS] },
770
+ ],
771
+ },
772
+ ],
773
+ ["b2d.body.create_fixture:return:fixture", { kind: "object", fields: B2D_FIXTURE_INFO_FIELDS }],
774
+ [
775
+ "b2d.body.create_chain:return:segments",
776
+ { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS },
777
+ ],
778
+ [
779
+ "b2d.body.get_fixtures:return:fixtures",
780
+ { kind: "array-object", fields: B2D_FIXTURE_INFO_FIELDS },
781
+ ],
782
+ // get_joints returns an array of opaque b2Joint handles (numbers).
783
+ ["b2d.body.get_joints:return:joints", { kind: "array", element: "number" }],
784
+ ["b2d.body.get_mass_data:return:data", { kind: "object", fields: B2D_MASS_DATA_FIELDS }],
785
+ ["b2d.body.set_mass_data:param:data", { kind: "object", fields: B2D_MASS_DATA_FIELDS }],
786
+ ["b2d.body.get_shapes:return:shapes", { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS }],
787
+ [
788
+ "b2d.body.get_transform:return:transform",
789
+ {
790
+ kind: "object",
791
+ fields: [
792
+ { name: "position", types: ["vector3"] },
793
+ { name: "angle", types: ["number"] },
794
+ ],
795
+ },
796
+ ],
797
+ // b2d.chain
798
+ [
799
+ "b2d.chain.get_segments:return:segments",
800
+ { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS },
801
+ ],
802
+ // b2d.fixture
803
+ ["b2d.fixture.get_aabb:return:aabb", { kind: "object", fields: B2D_AABB_FIELDS }],
804
+ ["b2d.fixture.get_filter_data:return:filter", { kind: "object", fields: B2D_FILTER_DATA_FIELDS }],
805
+ ["b2d.fixture.set_filter_data:param:filter", { kind: "object", fields: B2D_FILTER_DATA_FIELDS }],
806
+ ["b2d.fixture.get_shape:return:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
807
+ ["b2d.fixture.set_shape:param:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
808
+ // b2d.shape
809
+ ["b2d.shape.get_shape:return:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
810
+ ["b2d.shape.set_shape:param:definition", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
811
+ ["b2d.shape.get_mass_data:return:data", { kind: "object", fields: B2D_MASS_DATA_FIELDS }],
812
+ [
813
+ "b2d.shape.get_sensor_overlaps:return:overlaps",
814
+ { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS },
815
+ ],
816
+ [
817
+ "b2d.shape.ray_cast:return:hit",
818
+ {
819
+ kind: "object",
820
+ fields: [
821
+ { name: "point", types: ["vector3"] },
822
+ { name: "normal", types: ["vector3"] },
823
+ { name: "fraction", types: ["number"] },
824
+ { name: "iterations", types: ["number"] },
825
+ ],
826
+ },
827
+ ],
828
+ // b2d.joint create_* definitions (each an optional, joint-type-specific option
829
+ // table; every field is optional because the whole `definition` param is).
830
+ [
831
+ "b2d.joint.create_distance:param:definition",
832
+ {
833
+ kind: "object",
834
+ fields: [
835
+ { name: "local_anchor_a", types: ["vector3"] },
836
+ { name: "local_anchor_b", types: ["vector3"] },
837
+ { name: "length", types: ["number"] },
838
+ { name: "frequency", types: ["number"] },
839
+ { name: "damping_ratio", types: ["number"] },
840
+ { name: "collide_connected", types: ["boolean"] },
841
+ ],
842
+ },
843
+ ],
844
+ [
845
+ "b2d.joint.create_mouse:param:definition",
846
+ {
847
+ kind: "object",
848
+ fields: [
849
+ { name: "target", types: ["vector3"] },
850
+ { name: "max_force", types: ["number"] },
851
+ { name: "frequency", types: ["number"] },
852
+ { name: "damping_ratio", types: ["number"] },
853
+ { name: "collide_connected", types: ["boolean"] },
854
+ ],
855
+ },
856
+ ],
857
+ [
858
+ "b2d.joint.create_prismatic:param:definition",
859
+ {
860
+ kind: "object",
861
+ fields: [
862
+ { name: "local_anchor_a", types: ["vector3"] },
863
+ { name: "local_anchor_b", types: ["vector3"] },
864
+ { name: "local_axis_a", types: ["vector3"] },
865
+ { name: "reference_angle", types: ["number"] },
866
+ { name: "enable_limit", types: ["boolean"] },
867
+ { name: "lower_translation", types: ["number"] },
868
+ { name: "upper_translation", types: ["number"] },
869
+ { name: "enable_motor", types: ["boolean"] },
870
+ { name: "max_motor_force", types: ["number"] },
871
+ { name: "motor_speed", types: ["number"] },
872
+ { name: "collide_connected", types: ["boolean"] },
873
+ ],
874
+ },
875
+ ],
876
+ [
877
+ "b2d.joint.create_revolute:param:definition",
878
+ {
879
+ kind: "object",
880
+ fields: [
881
+ { name: "local_anchor_a", types: ["vector3"] },
882
+ { name: "local_anchor_b", types: ["vector3"] },
883
+ { name: "reference_angle", types: ["number"] },
884
+ { name: "enable_limit", types: ["boolean"] },
885
+ { name: "lower_angle", types: ["number"] },
886
+ { name: "upper_angle", types: ["number"] },
887
+ { name: "enable_motor", types: ["boolean"] },
888
+ { name: "max_motor_torque", types: ["number"] },
889
+ { name: "motor_speed", types: ["number"] },
890
+ { name: "collide_connected", types: ["boolean"] },
891
+ ],
892
+ },
893
+ ],
894
+ [
895
+ "b2d.joint.create_weld:param:definition",
896
+ {
897
+ kind: "object",
898
+ fields: [
899
+ { name: "local_anchor_a", types: ["vector3"] },
900
+ { name: "local_anchor_b", types: ["vector3"] },
901
+ { name: "reference_angle", types: ["number"] },
902
+ { name: "frequency", types: ["number"] },
903
+ { name: "damping_ratio", types: ["number"] },
904
+ { name: "collide_connected", types: ["boolean"] },
905
+ ],
906
+ },
907
+ ],
908
+ [
909
+ "b2d.joint.create_wheel:param:definition",
910
+ {
911
+ kind: "object",
912
+ fields: [
913
+ { name: "local_anchor_a", types: ["vector3"] },
914
+ { name: "local_anchor_b", types: ["vector3"] },
915
+ { name: "local_axis_a", types: ["vector3"] },
916
+ { name: "enable_motor", types: ["boolean"] },
917
+ { name: "max_motor_torque", types: ["number"] },
918
+ { name: "motor_speed", types: ["number"] },
919
+ { name: "frequency", types: ["number"] },
920
+ { name: "damping_ratio", types: ["number"] },
921
+ { name: "collide_connected", types: ["boolean"] },
922
+ ],
923
+ },
924
+ ],
925
+ [
926
+ "b2d.joint.create_friction:param:definition",
927
+ {
928
+ kind: "object",
929
+ fields: [
930
+ { name: "local_anchor_a", types: ["vector3"] },
931
+ { name: "local_anchor_b", types: ["vector3"] },
932
+ { name: "max_force", types: ["number"] },
933
+ { name: "max_torque", types: ["number"] },
934
+ { name: "collide_connected", types: ["boolean"] },
935
+ ],
936
+ },
937
+ ],
938
+ [
939
+ "b2d.joint.create_rope:param:definition",
940
+ {
941
+ kind: "object",
942
+ fields: [
943
+ { name: "local_anchor_a", types: ["vector3"] },
944
+ { name: "local_anchor_b", types: ["vector3"] },
945
+ { name: "max_length", types: ["number"] },
946
+ { name: "collide_connected", types: ["boolean"] },
947
+ ],
948
+ },
949
+ ],
950
+ [
951
+ "b2d.joint.create_pulley:param:definition",
952
+ {
953
+ kind: "object",
954
+ fields: [
955
+ { name: "ground_anchor_a", types: ["vector3"] },
956
+ { name: "ground_anchor_b", types: ["vector3"] },
957
+ { name: "local_anchor_a", types: ["vector3"] },
958
+ { name: "local_anchor_b", types: ["vector3"] },
959
+ { name: "length_a", types: ["number"] },
960
+ { name: "length_b", types: ["number"] },
961
+ { name: "ratio", types: ["number"] },
962
+ { name: "collide_connected", types: ["boolean"] },
963
+ ],
964
+ },
965
+ ],
966
+ [
967
+ "b2d.joint.create_gear:param:definition",
968
+ { kind: "object", fields: [{ name: "ratio", types: ["number"] }] },
969
+ ],
970
+ [
971
+ "b2d.joint.create_motor:param:definition",
972
+ {
973
+ kind: "object",
974
+ fields: [
975
+ { name: "linear_offset", types: ["vector3"] },
976
+ { name: "angular_offset", types: ["number"] },
977
+ { name: "max_force", types: ["number"] },
978
+ { name: "max_torque", types: ["number"] },
979
+ { name: "correction_factor", types: ["number"] },
980
+ { name: "collide_connected", types: ["boolean"] },
981
+ ],
982
+ },
983
+ ],
984
+ // b2d.world broad-phase queries + movers.
985
+ ["b2d.world.overlap_aabb:param:aabb", { kind: "object", fields: B2D_AABB_FIELDS }],
986
+ ["b2d.world.overlap_aabb:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
987
+ [
988
+ "b2d.world.overlap_aabb:return:fixtures",
989
+ { kind: "array-object", fields: B2D_FIXTURE_INFO_FIELDS },
990
+ ],
991
+ ["b2d.world.overlap_aabb:return:hits", { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS }],
992
+ ["b2d.world.overlap_aabb:return:stats", { kind: "object", fields: B2D_QUERY_STATS_FIELDS }],
993
+ ["b2d.world.overlap_shape:param:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
994
+ ["b2d.world.overlap_shape:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
995
+ [
996
+ "b2d.world.overlap_shape:return:fixtures",
997
+ { kind: "array-object", fields: B2D_FIXTURE_INFO_FIELDS },
998
+ ],
999
+ ["b2d.world.overlap_shape:return:hits", { kind: "array-object", fields: B2D_SHAPE_INFO_FIELDS }],
1000
+ ["b2d.world.overlap_shape:return:stats", { kind: "object", fields: B2D_QUERY_STATS_FIELDS }],
1001
+ ["b2d.world.cast_ray:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
1002
+ ["b2d.world.cast_ray:return:hits", { kind: "array-object", fields: B2D_CAST_HIT_FIELDS }],
1003
+ ["b2d.world.cast_ray:return:stats", { kind: "object", fields: B2D_QUERY_STATS_FIELDS }],
1004
+ ["b2d.world.cast_ray_closest:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
1005
+ [
1006
+ "b2d.world.cast_ray_closest:return:hit",
1007
+ {
1008
+ kind: "object",
1009
+ fields: [
1010
+ { name: "fixture", types: ["number"] },
1011
+ { name: "shape", types: ["number"] },
1012
+ { name: "point", types: ["vector3"] },
1013
+ { name: "normal", types: ["vector3"] },
1014
+ { name: "fraction", types: ["number"] },
1015
+ { name: "node_visits", types: ["number"] },
1016
+ { name: "leaf_visits", types: ["number"] },
1017
+ ],
1018
+ },
1019
+ ],
1020
+ ["b2d.world.cast_shape:param:shape", { kind: "object", fields: B2D_SHAPE_TABLE_FIELDS }],
1021
+ ["b2d.world.cast_shape:param:filter", { kind: "object", fields: B2D_QUERY_FILTER_FIELDS }],
1022
+ ["b2d.world.cast_shape:return:hits", { kind: "array-object", fields: B2D_CAST_HIT_FIELDS }],
1023
+ ["b2d.world.cast_shape:return:stats", { kind: "object", fields: B2D_QUERY_STATS_FIELDS }],
1024
+ ["b2d.world.cast_mover:param:capsule", { kind: "object", fields: B2D_CAPSULE_FIELDS }],
1025
+ ["b2d.world.cast_mover:param:filter", { kind: "object", fields: B2D_MOVER_FILTER_FIELDS }],
1026
+ ["b2d.world.collide_mover:param:capsule", { kind: "object", fields: B2D_CAPSULE_FIELDS }],
1027
+ ["b2d.world.collide_mover:param:filter", { kind: "object", fields: B2D_MOVER_FILTER_FIELDS }],
563
1028
  ]);
564
1029
 
565
1030
  // Slot-keyed (`element:param:name`, mirroring TABLE_SLOT_CURATIONS) replacements
@@ -600,6 +1065,43 @@ export const NESTED_FIELD_CURATIONS: ReadonlyMap<string, readonly TableField[]>
600
1065
  ["resource.get_atlas:return:data:geometries", ATLAS_GEOMETRY_MEMBERS],
601
1066
  ]);
602
1067
 
1068
+ // The shader-constant / vertex-attribute `value` field is documented as
1069
+ // `vmath.vector4 | vmath.vector3 | vmath.matrix4 | number | table`, where the
1070
+ // trailing `table` is the *array* form ("a table of vmath.vector4 or
1071
+ // vmath.matrix4" for constants, "a table of numbers" for matrix attributes). The
1072
+ // parser recovers every other field faithfully but leaves that `table` token as
1073
+ // a bare `Record`; this pins only the `value` field's TS type to the documented
1074
+ // array union while every sibling field stays parser-authoritative. Keyed by
1075
+ // `<element>:<kind>:<slot>:<field>`, mirroring NESTED_FIELD_CURATIONS.
1076
+ const CONSTANT_VALUE_TS = "Vector4 | Vector3 | Matrix4 | number | (Vector4 | Matrix4)[]";
1077
+ const ATTRIBUTE_VALUE_TS = "Vector4 | Vector3 | Matrix4 | number | number[]";
1078
+ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
1079
+ ["compute.set_constants:param:constants:value", CONSTANT_VALUE_TS],
1080
+ ["material.set_constants:param:constants:value", CONSTANT_VALUE_TS],
1081
+ ["material.set_vertex_attributes:param:attributes:value", ATTRIBUTE_VALUE_TS],
1082
+ ["material.get_vertex_attributes:return:table:value", ATTRIBUTE_VALUE_TS],
1083
+ ]);
1084
+
1085
+ // Pin a parser-recovered field's TS type from TABLE_FIELD_TYPE_OVERRIDES,
1086
+ // leaving every other field parser-authoritative. A field the override does not
1087
+ // name is returned unchanged; a field it names gains a `tsType` (emitted
1088
+ // verbatim, treated as recovered by the audit). Returns a new array; never
1089
+ // mutates the parser's result. Mirrors applyNestedFieldCurations.
1090
+ export function applyFieldTypeOverrides(
1091
+ elementName: string,
1092
+ slotKind: "param" | "return" | undefined,
1093
+ slotName: string | undefined,
1094
+ fields: readonly TableField[],
1095
+ ): TableField[] {
1096
+ if (slotKind === undefined || slotName === undefined) return [...fields];
1097
+ return fields.map((field) => {
1098
+ const override = TABLE_FIELD_TYPE_OVERRIDES.get(
1099
+ `${elementName}:${slotKind}:${slotName}:${field.name}`,
1100
+ );
1101
+ return override === undefined ? field : { ...field, tsType: override };
1102
+ });
1103
+ }
1104
+
603
1105
  /**
604
1106
  * Recover a Defold `function(...)` callback-signature token into a TypeScript
605
1107
  * function type. The token carries parameter names but no inner types, so each
@@ -631,16 +1133,30 @@ export function recoverCallbackSignature(token: string): string | null {
631
1133
  const TABLE_FIELD =
632
1134
  /<dt>\s*<code>([^<]+)<\/code>\s*<\/dt>\s*<dd>\s*<span class="type">([^<]+)<\/span>([^<]*)/g;
633
1135
  const LIST_PROSE = /\ba list of\b/i;
634
- // The slot-level array signal ("an array of …" / "a list of …") lives in the
635
- // prose preceding the field list, never in a field's own `<dd>` (that is the
636
- // per-field `LIST_PROSE` path). `isSlotLevelList` therefore tests only the
637
- // substring before the first `<dl>`/`<ul>`/`<li>`, so a field-internal list
638
- // marker can never wrap the whole slot.
639
- export const SLOT_LEVEL_LIST_PROSE = /\b(an?\s+array of|a\s+list of)\b/i;
640
- function isSlotLevelList(doc: string): boolean {
1136
+ // The slot-level array signal ("an array of …" / "a list of …" / the
1137
+ // records-collection "table of tables" form) lives in the prose preceding the
1138
+ // field list, never in a field's own `<dd>` (that is the per-field `LIST_PROSE`
1139
+ // path). `isSlotLevelList` therefore tests only the substring before the first
1140
+ // `<dl>`/`<ul>`/`<li>`, so a field-internal list marker can never wrap the whole
1141
+ // slot. The `tables? of tables?` clause wraps the compute/material getters whose
1142
+ // return doc opens "A table of tables, where each entry …".
1143
+ export const SLOT_LEVEL_LIST_PROSE = /\b(an?\s+array of|a\s+list of|tables?\s+of\s+tables?)\b/i;
1144
+ export function isSlotLevelList(doc: string): boolean {
641
1145
  const prefix = doc.split(/<dl>|<ul>|<li>/i)[0] ?? "";
642
1146
  return SLOT_LEVEL_LIST_PROSE.test(prefix);
643
1147
  }
1148
+ // The oracle for the fidelity audit: a records-collection return slot is any
1149
+ // "array/list/collection of tables" or "table of tables" form. Intentionally
1150
+ // broader than `SLOT_LEVEL_LIST_PROSE` (which names only the forms the emitter
1151
+ // actually wraps) so a records-collection wording the wrap trigger stops
1152
+ // recognizing re-surfaces as a counted loss, keeping the gate and the emitted
1153
+ // surface coupled. Scoped to the prose before the field list, same as above.
1154
+ export const RECORDS_COLLECTION_PROSE =
1155
+ /\b(an?\s+(?:array|list|collection)\s+of\s+tables?|tables?\s+of\s+tables?)\b/i;
1156
+ export function isRecordsCollectionSlot(doc: string): boolean {
1157
+ const prefix = doc.split(/<dl>|<ul>|<li>/i)[0] ?? "";
1158
+ return RECORDS_COLLECTION_PROSE.test(prefix);
1159
+ }
644
1160
  const FLATTENED_TABLE = /<li>\s*<dl>/;
645
1161
  // A number-list slot's element type is read from the brace form a "a list of …"
646
1162
  // `<dd>` ends with: `in the form {px0, py0, ..., pxn, pyn}` / `{i0, i1, ..., in}`.
@@ -726,6 +1242,12 @@ export interface TableField {
726
1242
  isList?: boolean;
727
1243
  numberList?: boolean;
728
1244
  optional?: boolean;
1245
+ // A hand-curated field whose faithful TS shape is not a plain token union
1246
+ // (e.g. a nested vector array, or a documented value union with an array
1247
+ // branch): inlineTableType emits this verbatim and the audit treats it as
1248
+ // recovered. Only ever set on a curation-authored field, with already-mapped
1249
+ // TS names — never from the parser — so it introduces no unmapped token.
1250
+ tsType?: string;
729
1251
  }
730
1252
 
731
1253
  function parseUlFields(doc: string): TableField[] {
@@ -1069,6 +1591,114 @@ export function emitDeclarations(module: ApiModule, options?: EmitOptions): stri
1069
1591
  return `${lines.join("\n")}\n`;
1070
1592
  }
1071
1593
 
1594
+ /** One emitted symbol's identity paired with its rendered TS signature text. */
1595
+ export interface SymbolSignature {
1596
+ readonly identity: ApiSymbolIdentity;
1597
+ readonly tsSignature: string;
1598
+ }
1599
+
1600
+ /**
1601
+ * Render each emitted symbol's authoritative TS signature through the same
1602
+ * prepare/emit helpers {@link emitDeclarations} uses, keyed by the identity the
1603
+ * availability matrix joins on. This is the single declaration-backed source for
1604
+ * the shared `api-signatures.json` artifact: the same `mapType` (constant
1605
+ * branding) and table-doc resolver produce text that appears verbatim in the
1606
+ * committed `.d.ts`. The caller applies the same `skipFunctions` filter the
1607
+ * `.d.ts` generation does, so a dropped member never yields a signature.
1608
+ */
1609
+ export function emitSymbolSignatures(module: ApiModule, options?: EmitOptions): SymbolSignature[] {
1610
+ const prefix = `${module.namespace}.`;
1611
+ const constantFqns = new Set(module.constants.map((c) => c.name));
1612
+ const knownConstantFqns = options?.knownConstantFqns;
1613
+ const baseMapType = options?.mapType ?? defaultMapType;
1614
+ const mapType = (token: string): string =>
1615
+ constantFqns.has(token) || knownConstantFqns?.has(token)
1616
+ ? brandType(token)
1617
+ : baseMapType(token);
1618
+ const resolver = buildTableDocResolver(
1619
+ module.functions.map((fn) => ({
1620
+ name: fn.name,
1621
+ slots: [...fn.parameters, ...fn.returnValues],
1622
+ })),
1623
+ );
1624
+
1625
+ const out: SymbolSignature[] = [];
1626
+ const fnIdentity = (fn: ApiFunction): ApiSymbolIdentity => ({
1627
+ namespace: module.namespace,
1628
+ kind: "FUNCTION",
1629
+ name: fn.name,
1630
+ signature: normalizedFunctionSignature(fn),
1631
+ });
1632
+ const emitName = (name: string): string => (TS_RESERVED_NAMES.has(name) ? `_${name}` : name);
1633
+
1634
+ for (const fn of module.functions) {
1635
+ const prepared = prepareFunction(fn, prefix);
1636
+ if (prepared === null) continue;
1637
+ out.push({
1638
+ identity: fnIdentity(fn),
1639
+ tsSignature: emitFunction(prepared, emitName(prepared.name), mapType, resolver),
1640
+ });
1641
+ }
1642
+
1643
+ const nestedFunctionLocal = /^[A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*$/;
1644
+ for (const fn of module.functions) {
1645
+ const local = stripPrefix(fn.name, prefix);
1646
+ if (!nestedFunctionLocal.test(local)) continue;
1647
+ const segment = local.slice(0, local.indexOf("."));
1648
+ const prepared = prepareFunction(fn, `${module.namespace}.${segment}.`);
1649
+ if (prepared === null) continue;
1650
+ out.push({
1651
+ identity: fnIdentity(fn),
1652
+ tsSignature: emitFunction(prepared, prepared.name, mapType, resolver),
1653
+ });
1654
+ }
1655
+
1656
+ const handleGroups = collectHandleMethodGroups(module);
1657
+ for (const group of handleGroups.values()) {
1658
+ for (const prepared of group) {
1659
+ out.push({
1660
+ identity: fnIdentity(prepared.original),
1661
+ tsSignature: emitMethod(prepared, mapType, resolver),
1662
+ });
1663
+ }
1664
+ }
1665
+
1666
+ for (const c of module.constants) {
1667
+ const prepared = prepareConstant(c, prefix);
1668
+ if (prepared === null) continue;
1669
+ out.push({
1670
+ identity: { namespace: module.namespace, kind: "CONSTANT", name: c.name, signature: "" },
1671
+ tsSignature: `const ${prepared.name}: ${brandType(prepared.fqn)};`,
1672
+ });
1673
+ }
1674
+
1675
+ for (const v of module.variables) {
1676
+ const prepared = prepareVariable(v, prefix);
1677
+ if (prepared === null) continue;
1678
+ out.push({
1679
+ identity: { namespace: module.namespace, kind: "VARIABLE", name: v.name, signature: "" },
1680
+ tsSignature: emitVariable(prepared, emitName(prepared.name), mapType),
1681
+ });
1682
+ }
1683
+
1684
+ for (const p of module.properties) {
1685
+ out.push({
1686
+ identity: { namespace: module.namespace, kind: "PROPERTY", name: p.name, signature: "" },
1687
+ tsSignature: emitPropertyMember(p, mapType),
1688
+ });
1689
+ }
1690
+
1691
+ for (const t of module.typedefs) {
1692
+ if (!TS_IDENTIFIER.test(t.name) || handleGroups.has(t.name)) continue;
1693
+ out.push({
1694
+ identity: { namespace: module.namespace, kind: "TYPEDEF", name: t.name, signature: "" },
1695
+ tsSignature: `type ${t.name} = Opaque<"${t.name}">;`,
1696
+ });
1697
+ }
1698
+
1699
+ return out;
1700
+ }
1701
+
1072
1702
  interface PreparedConstant {
1073
1703
  name: string;
1074
1704
  fqn: string;
@@ -1403,10 +2033,24 @@ function mapSlotUnion(
1403
2033
  } else if (curation?.kind === "object" || curation?.kind === "array-object") {
1404
2034
  const object = inlineTableType(curation.fields, mapType, optionalFields);
1405
2035
  ts = curation.kind === "array-object" ? `${object}[]` : object;
2036
+ } else if (curation?.kind === "keyed-object") {
2037
+ // The doc's `<dl>` describes the value shape; parse it and re-key by an
2038
+ // arbitrary name. Reusing the parser keeps the inner fields in lockstep
2039
+ // with the doc instead of a hand-listed drift-prone copy; a documented
2040
+ // array-form field is pinned via TABLE_FIELD_TYPE_OVERRIDES.
2041
+ const parsed = applyFieldTypeOverrides(
2042
+ elementName,
2043
+ slotKind,
2044
+ slotName,
2045
+ parseTableFields(doc, resolver) ?? [],
2046
+ );
2047
+ const object = inlineTableType(parsed, mapType, optionalFields);
2048
+ ts = `Record<string, ${object}>`;
1406
2049
  } else {
1407
2050
  const parsed = parseTableFields(doc, resolver);
1408
2051
  if (parsed !== null) {
1409
- const fields = applyNestedFieldCurations(elementName, slotKind, slotName, parsed);
2052
+ const nested = applyNestedFieldCurations(elementName, slotKind, slotName, parsed);
2053
+ const fields = applyFieldTypeOverrides(elementName, slotKind, slotName, nested);
1410
2054
  const object = inlineTableType(fields, mapType, optionalFields);
1411
2055
  ts = isSlotLevelList(doc) ? `${object}[]` : object;
1412
2056
  } else {
@@ -1488,14 +2132,19 @@ export function inlineTableType(
1488
2132
  // number-list field carries no member shape but a machine-readable numeric
1489
2133
  // element type ("in the form {px0, …}"), so it emits `number[]`.
1490
2134
  const ts =
1491
- field.fields !== undefined
1492
- ? `${inlineTableType(field.fields, mapType, optionalFields)}${field.isList ? "[]" : ""}`
1493
- : field.numberList === true
1494
- ? "number[]"
1495
- : field.types.length > 0
1496
- ? unionFromTokens(field.types, mapType)
1497
- : "unknown";
1498
- return `${key}${optionalFields ? "?" : ""}: ${ts}`;
2135
+ field.tsType !== undefined
2136
+ ? field.tsType
2137
+ : field.fields !== undefined
2138
+ ? `${inlineTableType(field.fields, mapType, optionalFields)}${field.isList ? "[]" : ""}`
2139
+ : field.numberList === true
2140
+ ? "number[]"
2141
+ : field.types.length > 0
2142
+ ? unionFromTokens(field.types, mapType)
2143
+ : "unknown";
2144
+ // A field is optional when the whole slot is param-side (optionalFields) or
2145
+ // when the curation marks that individual field optional (a return-side
2146
+ // variant field present only for some shape kinds, e.g. a b2d shape table).
2147
+ return `${key}${optionalFields || field.optional === true ? "?" : ""}: ${ts}`;
1499
2148
  });
1500
2149
  return `{ ${members.join("; ")} }`;
1501
2150
  }
@@ -1512,6 +2161,14 @@ function unionFromTokens(tokens: readonly string[], mapType: (t: string) => stri
1512
2161
  return mapped.join(" | ");
1513
2162
  }
1514
2163
 
2164
+ export function isKnownDefoldTypeToken(token: string): boolean {
2165
+ return (
2166
+ token === "nil" ||
2167
+ Object.hasOwn(DEFOLD_TYPE_MAP, token) ||
2168
+ recoverCallbackSignature(token) !== null
2169
+ );
2170
+ }
2171
+
1515
2172
  function defaultMapType(token: string): string {
1516
2173
  if (Object.hasOwn(DEFOLD_TYPE_MAP, token)) {
1517
2174
  const mapped = DEFOLD_TYPE_MAP[token];