@defold-typescript/types 0.52.0 → 0.53.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 (191) hide show
  1. package/api-signatures.json +2074 -1964
  2. package/examples/translations.json +15 -15
  3. package/generated/b2d.d.ts +39 -39
  4. package/generated/b2d_body.d.ts +11 -11
  5. package/generated/b2d_chain.d.ts +3 -3
  6. package/generated/b2d_fixture.d.ts +5 -5
  7. package/generated/b2d_joint.d.ts +29 -29
  8. package/generated/b2d_shape.d.ts +13 -13
  9. package/generated/b2d_world.d.ts +15 -15
  10. package/generated/buffer.d.ts +5 -6
  11. package/generated/bullet3d.d.ts +6 -6
  12. package/generated/bullet3d_collision_object.d.ts +9 -9
  13. package/generated/bullet3d_constraint.d.ts +78 -78
  14. package/generated/bullet3d_rigid_body.d.ts +3 -3
  15. package/generated/bullet3d_shape.d.ts +11 -11
  16. package/generated/bullet3d_world.d.ts +20 -20
  17. package/generated/camera.d.ts +4 -4
  18. package/generated/collectionfactory.d.ts +2 -3
  19. package/generated/collectionproxy.d.ts +5 -5
  20. package/generated/compute.d.ts +5 -5
  21. package/generated/crash.d.ts +7 -7
  22. package/generated/editor-vm/http.d.ts +13 -13
  23. package/generated/editor-vm/image.d.ts +4 -4
  24. package/generated/editor-vm/json.d.ts +1 -1
  25. package/generated/editor-vm/tilemap_tiles.d.ts +9 -9
  26. package/generated/editor-vm/zip.d.ts +4 -4
  27. package/generated/editor.d.ts +183 -183
  28. package/generated/factory.d.ts +2 -3
  29. package/generated/font.d.ts +4 -4
  30. package/generated/go.d.ts +10 -12
  31. package/generated/graphics.d.ts +23 -26
  32. package/generated/gui.d.ts +44 -58
  33. package/generated/http.d.ts +2 -2
  34. package/generated/image.d.ts +12 -12
  35. package/generated/json.d.ts +4 -4
  36. package/generated/label.d.ts +5 -5
  37. package/generated/liveupdate.d.ts +3 -3
  38. package/generated/material.d.ts +48 -48
  39. package/generated/model.d.ts +46 -14
  40. package/generated/particlefx.d.ts +5 -5
  41. package/generated/physics.d.ts +19 -19
  42. package/generated/profiler.d.ts +3 -5
  43. package/generated/render-script-modules/b2d.d.ts +39 -39
  44. package/generated/render-script-modules/bullet3d.d.ts +5 -5
  45. package/generated/render-script-modules/collectionfactory.d.ts +1 -2
  46. package/generated/render-script-modules/collectionproxy.d.ts +3 -3
  47. package/generated/render-script-modules/factory.d.ts +1 -2
  48. package/generated/render-script-modules/go.d.ts +8 -10
  49. package/generated/render-script-modules/label.d.ts +2 -2
  50. package/generated/render-script-modules/model.d.ts +43 -11
  51. package/generated/render-script-modules/particlefx.d.ts +3 -3
  52. package/generated/render-script-modules/physics.d.ts +12 -12
  53. package/generated/render-script-modules/resource.d.ts +52 -53
  54. package/generated/render-script-modules/sound.d.ts +4 -4
  55. package/generated/render-script-modules/sprite.d.ts +2 -2
  56. package/generated/render-script-modules/tilemap.d.ts +2 -2
  57. package/generated/render.d.ts +44 -45
  58. package/generated/resource.d.ts +58 -59
  59. package/generated/socket.d.ts +18 -23
  60. package/generated/sound.d.ts +6 -6
  61. package/generated/sprite.d.ts +3 -3
  62. package/generated/sys.d.ts +20 -21
  63. package/generated/tilemap.d.ts +3 -3
  64. package/generated/timer.d.ts +3 -3
  65. package/generated/versions/defold-1.12.4/b2d.d.ts +2 -2
  66. package/generated/versions/defold-1.12.4/b2d_body.d.ts +2 -2
  67. package/generated/versions/defold-1.12.4/go.d.ts +2 -2
  68. package/generated/versions/defold-1.12.4/graphics.d.ts +1 -1
  69. package/generated/versions/defold-1.12.4/gui.d.ts +18 -19
  70. package/generated/versions/defold-1.12.4/label.d.ts +1 -1
  71. package/generated/versions/defold-1.12.4/model.d.ts +42 -10
  72. package/generated/versions/defold-1.12.4/particlefx.d.ts +1 -1
  73. package/generated/versions/defold-1.12.4/physics.d.ts +1 -1
  74. package/generated/versions/defold-1.12.4/render.d.ts +5 -5
  75. package/generated/versions/defold-1.12.4/resource.d.ts +8 -8
  76. package/generated/versions/defold-1.12.4/socket.d.ts +12 -12
  77. package/generated/versions/defold-1.12.4/sound.d.ts +1 -1
  78. package/generated/versions/defold-1.12.4/sprite.d.ts +1 -1
  79. package/generated/versions/defold-1.12.4/sys.d.ts +2 -2
  80. package/generated/versions/defold-1.12.4/tilemap.d.ts +1 -1
  81. package/generated/versions/defold-1.12.4/window.d.ts +3 -4
  82. package/generated/versions/defold-1.13.0/b2d.d.ts +2 -2
  83. package/generated/versions/defold-1.13.0/b2d_body.d.ts +2 -2
  84. package/generated/versions/defold-1.13.0/b2d_chain.d.ts +1 -1
  85. package/generated/versions/defold-1.13.0/b2d_joint.d.ts +1 -1
  86. package/generated/versions/defold-1.13.0/b2d_shape.d.ts +1 -1
  87. package/generated/versions/defold-1.13.0/camera.d.ts +1 -1
  88. package/generated/versions/defold-1.13.0/go.d.ts +2 -2
  89. package/generated/versions/defold-1.13.0/graphics.d.ts +1 -1
  90. package/generated/versions/defold-1.13.0/gui.d.ts +18 -19
  91. package/generated/versions/defold-1.13.0/label.d.ts +1 -1
  92. package/generated/versions/defold-1.13.0/model.d.ts +42 -10
  93. package/generated/versions/defold-1.13.0/particlefx.d.ts +1 -1
  94. package/generated/versions/defold-1.13.0/physics.d.ts +1 -1
  95. package/generated/versions/defold-1.13.0/render.d.ts +5 -5
  96. package/generated/versions/defold-1.13.0/resource.d.ts +8 -8
  97. package/generated/versions/defold-1.13.0/socket.d.ts +12 -12
  98. package/generated/versions/defold-1.13.0/sound.d.ts +1 -1
  99. package/generated/versions/defold-1.13.0/sprite.d.ts +1 -1
  100. package/generated/versions/defold-1.13.0/sys.d.ts +2 -2
  101. package/generated/versions/defold-1.13.0/tilemap.d.ts +1 -1
  102. package/generated/versions/defold-1.13.0/window.d.ts +3 -4
  103. package/generated/versions/defold-1.13.1/b2d.d.ts +2 -2
  104. package/generated/versions/defold-1.13.1/b2d_body.d.ts +2 -2
  105. package/generated/versions/defold-1.13.1/b2d_chain.d.ts +1 -1
  106. package/generated/versions/defold-1.13.1/b2d_joint.d.ts +1 -1
  107. package/generated/versions/defold-1.13.1/b2d_shape.d.ts +1 -1
  108. package/generated/versions/defold-1.13.1/camera.d.ts +1 -1
  109. package/generated/versions/defold-1.13.1/go.d.ts +2 -2
  110. package/generated/versions/defold-1.13.1/graphics.d.ts +1 -1
  111. package/generated/versions/defold-1.13.1/gui.d.ts +18 -19
  112. package/generated/versions/defold-1.13.1/label.d.ts +1 -1
  113. package/generated/versions/defold-1.13.1/model.d.ts +42 -10
  114. package/generated/versions/defold-1.13.1/particlefx.d.ts +1 -1
  115. package/generated/versions/defold-1.13.1/physics.d.ts +1 -1
  116. package/generated/versions/defold-1.13.1/render.d.ts +5 -5
  117. package/generated/versions/defold-1.13.1/resource.d.ts +8 -8
  118. package/generated/versions/defold-1.13.1/socket.d.ts +12 -12
  119. package/generated/versions/defold-1.13.1/sound.d.ts +1 -1
  120. package/generated/versions/defold-1.13.1/sprite.d.ts +1 -1
  121. package/generated/versions/defold-1.13.1/sys.d.ts +2 -2
  122. package/generated/versions/defold-1.13.1/tilemap.d.ts +1 -1
  123. package/generated/versions/defold-1.13.1/window.d.ts +3 -4
  124. package/generated/versions/defold-1.13.2/b2d.d.ts +39 -39
  125. package/generated/versions/defold-1.13.2/b2d_body.d.ts +11 -11
  126. package/generated/versions/defold-1.13.2/b2d_chain.d.ts +3 -3
  127. package/generated/versions/defold-1.13.2/b2d_fixture.d.ts +5 -5
  128. package/generated/versions/defold-1.13.2/b2d_joint.d.ts +29 -29
  129. package/generated/versions/defold-1.13.2/b2d_shape.d.ts +13 -13
  130. package/generated/versions/defold-1.13.2/b2d_world.d.ts +15 -15
  131. package/generated/versions/defold-1.13.2/buffer.d.ts +5 -6
  132. package/generated/versions/defold-1.13.2/bullet3d.d.ts +6 -6
  133. package/generated/versions/defold-1.13.2/bullet3d_collision_object.d.ts +9 -9
  134. package/generated/versions/defold-1.13.2/bullet3d_constraint.d.ts +78 -78
  135. package/generated/versions/defold-1.13.2/bullet3d_rigid_body.d.ts +3 -3
  136. package/generated/versions/defold-1.13.2/bullet3d_shape.d.ts +11 -11
  137. package/generated/versions/defold-1.13.2/bullet3d_world.d.ts +20 -20
  138. package/generated/versions/defold-1.13.2/camera.d.ts +4 -4
  139. package/generated/versions/defold-1.13.2/collectionfactory.d.ts +2 -3
  140. package/generated/versions/defold-1.13.2/collectionproxy.d.ts +5 -5
  141. package/generated/versions/defold-1.13.2/compute.d.ts +5 -5
  142. package/generated/versions/defold-1.13.2/crash.d.ts +7 -7
  143. package/generated/versions/defold-1.13.2/editor-vm/http.d.ts +13 -13
  144. package/generated/versions/defold-1.13.2/editor-vm/image.d.ts +4 -4
  145. package/generated/versions/defold-1.13.2/editor-vm/json.d.ts +1 -1
  146. package/generated/versions/defold-1.13.2/editor-vm/tilemap_tiles.d.ts +9 -9
  147. package/generated/versions/defold-1.13.2/editor-vm/zip.d.ts +4 -4
  148. package/generated/versions/defold-1.13.2/editor.d.ts +183 -183
  149. package/generated/versions/defold-1.13.2/factory.d.ts +2 -3
  150. package/generated/versions/defold-1.13.2/font.d.ts +4 -4
  151. package/generated/versions/defold-1.13.2/go.d.ts +10 -12
  152. package/generated/versions/defold-1.13.2/graphics.d.ts +22 -25
  153. package/generated/versions/defold-1.13.2/gui.d.ts +44 -58
  154. package/generated/versions/defold-1.13.2/http.d.ts +2 -2
  155. package/generated/versions/defold-1.13.2/image.d.ts +12 -12
  156. package/generated/versions/defold-1.13.2/json.d.ts +4 -4
  157. package/generated/versions/defold-1.13.2/label.d.ts +5 -5
  158. package/generated/versions/defold-1.13.2/liveupdate.d.ts +3 -3
  159. package/generated/versions/defold-1.13.2/material.d.ts +48 -48
  160. package/generated/versions/defold-1.13.2/model.d.ts +46 -14
  161. package/generated/versions/defold-1.13.2/particlefx.d.ts +5 -5
  162. package/generated/versions/defold-1.13.2/physics.d.ts +19 -19
  163. package/generated/versions/defold-1.13.2/profiler.d.ts +3 -5
  164. package/generated/versions/defold-1.13.2/render.d.ts +43 -44
  165. package/generated/versions/defold-1.13.2/resource.d.ts +58 -59
  166. package/generated/versions/defold-1.13.2/socket.d.ts +18 -23
  167. package/generated/versions/defold-1.13.2/sound.d.ts +6 -6
  168. package/generated/versions/defold-1.13.2/sprite.d.ts +3 -3
  169. package/generated/versions/defold-1.13.2/sys.d.ts +20 -21
  170. package/generated/versions/defold-1.13.2/tilemap.d.ts +3 -3
  171. package/generated/versions/defold-1.13.2/timer.d.ts +3 -3
  172. package/generated/versions/defold-1.13.2/vmath.d.ts +0 -1
  173. package/generated/versions/defold-1.13.2/window.d.ts +8 -10
  174. package/generated/vmath.d.ts +0 -1
  175. package/generated/window.d.ts +8 -10
  176. package/identity-vocabulary.json +41 -34
  177. package/package.json +2 -1
  178. package/scripts/generate-api-availability.ts +9 -3
  179. package/scripts/shared-handle-methods.ts +3 -2
  180. package/src/api-doc.ts +8 -0
  181. package/src/api-identity.ts +32 -1
  182. package/src/component-properties.d.ts +8 -8
  183. package/src/core-types.ts +29 -13
  184. package/src/doc-corrections.ts +9 -0
  185. package/src/emit-dts.ts +664 -202
  186. package/src/engine-type-name.ts +33 -0
  187. package/src/go-overloads.d.ts +10 -10
  188. package/src/gui-overloads.d.ts +11 -11
  189. package/src/index.ts +1 -0
  190. package/src/lifecycle.ts +2 -2
  191. package/src/socket-types.d.ts +3 -3
package/src/emit-dts.ts CHANGED
@@ -15,7 +15,7 @@ import type {
15
15
  ApiTypedef,
16
16
  ApiVariable,
17
17
  } from "./api-doc";
18
- import { DEFOLD_TYPE_MAP } from "./core-types";
18
+ import { DEFOLD_TYPE_MAP, SOCKET_HANDLE_NAMES, type SocketHandleReceiver } from "./core-types";
19
19
  import { correctionPins, pinMatches, propertyCorrectionPins } from "./correction-pins";
20
20
  import {
21
21
  type DocCommentParts,
@@ -26,6 +26,7 @@ import {
26
26
  summaryFor,
27
27
  } from "./doc-comment";
28
28
  import { applyMessageOverlays, parseMessagesDoc } from "./emit-messages";
29
+ import { engineTypeName } from "./engine-type-name";
29
30
  import type { TranslationStore } from "./example-store";
30
31
  import { lookupExampleSegments, translateProseFences } from "./example-store";
31
32
  import {
@@ -87,9 +88,10 @@ export interface EmitOptions {
87
88
  // bindings (a ref-doc target), where every entry the ref-doc omits is emitted.
88
89
  registeredConstants?: ReadonlySet<string>;
89
90
  // The `RETURN_TYPE_CORRECTIONS` and `STRUCT_MEMBER_ADDITIONS` keys whose binding
90
- // pin holds in the target's vendored engine source. A binding-pinned entry
91
- // applies only when its key is here, so a surface with no vendored bindings
92
- // (absent) applies none of them.
91
+ // pin holds in the target's vendored engine source, and the
92
+ // `PROPERTY_KEY_RANGES` keys whose `engine` pins all hold there. A pinned
93
+ // entry applies only when its key is here, so a surface with no vendored
94
+ // bindings (absent) applies none of them.
93
95
  bindingEvidence?: ReadonlySet<string>;
94
96
  // The 1-based parameter slots, keyed by function FQN, that the target's engine
95
97
  // bindings let a call omit but raise on when passed nil. Each optional one
@@ -554,7 +556,7 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
554
556
  [
555
557
  "bullet3d.shape.get_shape",
556
558
  {
557
- ts: "bullet3d.shape.definition | { type: typeof bullet3d.shape.SHAPE_TYPE_MESH }",
559
+ ts: "bullet3d.shape.Definition | { type: typeof bullet3d.shape.SHAPE_TYPE_MESH }",
558
560
  upstream: ["bullet3d.shape.definition"],
559
561
  reason:
560
562
  "script_bullet3d_shape.cpp:Shape_GetShape sets only `type` for a triangle mesh, which no shape definition arm matches",
@@ -658,6 +660,36 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
658
660
  },
659
661
  ] as const,
660
662
  ),
663
+ [
664
+ "gui.get_xanchor",
665
+ {
666
+ ts: "Exclude<gui.Anchor, typeof gui.ANCHOR_BOTTOM | typeof gui.ANCHOR_TOP>",
667
+ upstream: ["constant"],
668
+ retypedUpstream: [["gui.ANCHOR"]],
669
+ reason:
670
+ "gui_script.cpp:LuaGetXAnchor pushes GetNodeXAnchor, an x anchor: XANCHOR_NONE, XANCHOR_LEFT, or XANCHOR_RIGHT",
671
+ binding: {
672
+ file: "gui/src/gui_script.cpp",
673
+ cFunction: "LuaGetXAnchor",
674
+ contains: "lua_pushnumber(L, GetNodeXAnchor(scene, hnode));",
675
+ },
676
+ },
677
+ ],
678
+ [
679
+ "gui.get_yanchor",
680
+ {
681
+ ts: "Exclude<gui.Anchor, typeof gui.ANCHOR_LEFT | typeof gui.ANCHOR_RIGHT>",
682
+ upstream: ["constant"],
683
+ retypedUpstream: [["gui.ANCHOR"]],
684
+ reason:
685
+ "gui_script.cpp:LuaGetYAnchor pushes GetNodeYAnchor, a y anchor: YANCHOR_NONE, YANCHOR_TOP, or YANCHOR_BOTTOM",
686
+ binding: {
687
+ file: "gui/src/gui_script.cpp",
688
+ cFunction: "LuaGetYAnchor",
689
+ contains: "lua_pushnumber(L, GetNodeYAnchor(scene, hnode));",
690
+ },
691
+ },
692
+ ],
661
693
  ]);
662
694
 
663
695
  // A parameter whose declared `types` disagree with what the same element's
@@ -665,23 +697,29 @@ export const RETURN_TYPE_CORRECTIONS: ReadonlyMap<string, ReturnTypeCorrection>
665
697
  // target by resource name, a texture unit, a Lua sequence where `table` is
666
698
  // declared, a hash the binding refuses as a string. Admission rule: only a slot
667
699
  // that evidence contradicts; a slot that is merely imprecise is not a candidate.
668
- // The correction edits the mapped union rather than replacing it: `adds` appends
669
- // what the slot also takes and `removes` drops members the binding raises on, so
670
- // the URL address alias, array curation and constant mapping stay live, and
671
- // optionality stays with `isDocOptional`. Each entry records the upstream tokens
672
- // it contradicts, plus any later spelling of the same defect in
673
- // `retypedUpstream`, and applies only to a slot still declaring one of them;
674
- // `param-correction-provenance.test.ts` pins them against every retained
675
- // vendored ref-doc. Once no retained target still declares any of them the
676
- // entry is deleted, never re-pinned.
677
- export interface ParamTypeCorrection {
678
- readonly adds?: string;
679
- readonly removes?: readonly string[];
700
+ // A correction either replaces the mapped type exactly with `ts`, or edits the
701
+ // mapped union: `adds` appends what the slot also takes and `removes` drops
702
+ // members the binding raises on. Union edits keep the URL address alias, array
703
+ // curation and constant mapping live; optionality stays with `isDocOptional`.
704
+ // Each entry records the upstream tokens it contradicts, plus any later spelling
705
+ // of the same defect in `retypedUpstream`, and applies only to a slot still
706
+ // declaring one of them. An entry with a `binding` also requires its exact
707
+ // `EmitOptions.bindingEvidence` key. `param-correction-provenance.test.ts` pins
708
+ // the ref-doc tokens against every retained target; once no target declares a
709
+ // pin, the entry is deleted rather than re-pinned.
710
+ interface ParamTypeCorrectionBase {
680
711
  readonly upstream: readonly string[];
681
712
  readonly retypedUpstream?: readonly (readonly string[])[];
682
713
  readonly reason: string;
714
+ readonly binding?: BindingPin;
683
715
  }
684
716
 
717
+ export type ParamTypeCorrection = ParamTypeCorrectionBase &
718
+ (
719
+ | { readonly ts: string; readonly adds?: undefined; readonly removes?: undefined }
720
+ | { readonly ts?: undefined; readonly adds?: string; readonly removes?: readonly string[] }
721
+ );
722
+
685
723
  const CAMERA_ADDRESS_REASON =
686
724
  "render_script_camera.cpp:CheckRenderCamera resolves anything but a number handle through ResolveURL, which takes a string or a hash";
687
725
 
@@ -715,6 +753,58 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
715
753
  string,
716
754
  ParamTypeCorrection
717
755
  >([
756
+ [
757
+ "gui.set_xanchor:param:anchor",
758
+ {
759
+ ts: "Exclude<gui.Anchor, typeof gui.ANCHOR_BOTTOM | typeof gui.ANCHOR_TOP>",
760
+ upstream: ["constant"],
761
+ retypedUpstream: [["gui.ANCHOR"]],
762
+ reason:
763
+ "gui_script.cpp:LuaSetXAnchor rejects every value except XANCHOR_NONE, XANCHOR_LEFT, and XANCHOR_RIGHT",
764
+ binding: {
765
+ file: "gui/src/gui_script.cpp",
766
+ cFunction: "LuaSetXAnchor",
767
+ contains: "anchor != XANCHOR_NONE && anchor != XANCHOR_LEFT && anchor != XANCHOR_RIGHT",
768
+ },
769
+ },
770
+ ],
771
+ [
772
+ "gui.set_yanchor:param:anchor",
773
+ {
774
+ ts: "Exclude<gui.Anchor, typeof gui.ANCHOR_LEFT | typeof gui.ANCHOR_RIGHT>",
775
+ upstream: ["constant"],
776
+ retypedUpstream: [["gui.ANCHOR"]],
777
+ reason:
778
+ "gui_script.cpp:LuaSetYAnchor rejects every value except YANCHOR_NONE, YANCHOR_TOP, and YANCHOR_BOTTOM",
779
+ binding: {
780
+ file: "gui/src/gui_script.cpp",
781
+ cFunction: "LuaSetYAnchor",
782
+ contains: "anchor != YANCHOR_NONE && anchor != YANCHOR_TOP && anchor != YANCHOR_BOTTOM",
783
+ },
784
+ },
785
+ ],
786
+ [
787
+ "go.animate:param:playback",
788
+ {
789
+ ts: "go.Playback",
790
+ upstream: [
791
+ "go.PLAYBACK_ONCE_FORWARD",
792
+ "go.PLAYBACK_ONCE_BACKWARD",
793
+ "go.PLAYBACK_ONCE_PINGPONG",
794
+ "go.PLAYBACK_LOOP_FORWARD",
795
+ "go.PLAYBACK_LOOP_BACKWARD",
796
+ "go.PLAYBACK_LOOP_PINGPONG",
797
+ ],
798
+ retypedUpstream: [["go.PLAYBACK"]],
799
+ reason:
800
+ "gameobject_script.cpp:Script_Animate accepts every playback value below PLAYBACK_COUNT, including PLAYBACK_NONE",
801
+ binding: {
802
+ file: "gameobject/src/gameobject/gameobject_script.cpp",
803
+ cFunction: "Script_Animate",
804
+ contains: "if (playback >= PLAYBACK_COUNT)",
805
+ },
806
+ },
807
+ ],
718
808
  ...CAMERA_SLOT_FUNCTIONS.map(
719
809
  (name) =>
720
810
  [
@@ -921,8 +1011,8 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
921
1011
  [
922
1012
  `render.${name}:param:state`,
923
1013
  {
924
- adds: "graphics.State",
925
- removes: ["graphics.STATE"],
1014
+ adds: "Exclude<graphics.State, typeof graphics.STATE_ALPHA_TEST_SUPPORTED | typeof graphics.STATE_SCISSOR_TEST>",
1015
+ removes: ["graphics.State"],
926
1016
  upstream: ["graphics.STATE"],
927
1017
  reason: `render_script.cpp:RenderScript_${name === "enable_state" ? "Enable" : "Disable"}State raises "Invalid state" on any state but the six it lists, so graphics.STATE_SCISSOR_TEST and graphics.STATE_ALPHA_TEST_SUPPORTED are rejected`,
928
1018
  },
@@ -997,8 +1087,8 @@ export const PARAM_TYPE_CORRECTIONS: ReadonlyMap<string, ParamTypeCorrection> =
997
1087
  [
998
1088
  "window.set_dim_mode:param:mode",
999
1089
  {
1000
- removes: ["window.DIMMING"],
1001
- adds: "window.DimModeStateSettable",
1090
+ removes: ["window.Dimming"],
1091
+ adds: "Exclude<window.Dimming, typeof window.DIMMING_UNKNOWN>",
1002
1092
  upstream: ["window.DIMMING"],
1003
1093
  reason:
1004
1094
  'script_window.cpp:SetDimMode raises "The dim mode specified is not supported." for anything but DIMMING_ON and DIMMING_OFF',
@@ -1100,16 +1190,70 @@ export const PROPERTY_TYPE_CORRECTIONS: ReadonlyMap<string, PropertyTypeCorrecti
1100
1190
  // saying it, or when the phrase stops stating `first-last`. Curated rather than
1101
1191
  // matched on a trailing `N`, so each new upstream placeholder is a deliberate
1102
1192
  // addition.
1193
+ //
1194
+ // `engine.last` replaces `last` on a target whose vendored engine source shows
1195
+ // every one of `engine.pins` (the `EmitOptions.bindingEvidence` keys); every
1196
+ // other surface, including one generated from a ref-doc alone, keeps the range
1197
+ // `evidence` states.
1103
1198
  export interface PropertyKeyRange {
1104
1199
  readonly prefix: string;
1105
1200
  readonly first: number;
1106
1201
  readonly last: number;
1107
1202
  readonly evidence: string;
1203
+ readonly engine?: { readonly last: number; readonly pins: readonly EngineSourcePin[] };
1108
1204
  }
1109
1205
 
1110
- // Keyed `<namespace>.<property>`, mirroring PROPERTY_TYPE_CORRECTIONS.
1206
+ // A token snippet the target's vendored engine evidence must contain. With
1207
+ // `cFunction` it is matched in that function's body, without it anywhere in
1208
+ // the file.
1209
+ export interface EngineSourcePin {
1210
+ // Relative to the target's vendored `engine/` tree, as `BindingPin.file` is.
1211
+ readonly file: string;
1212
+ readonly cFunction?: string;
1213
+ readonly contains: string;
1214
+ }
1215
+
1216
+ const MODEL_TEXTURE_LOOP =
1217
+ "for (uint32_t i = 0; i < dmRender::RenderObject::MAX_TEXTURE_COUNT; ++i) { if (params.m_PropertyId == PROP_TEXTURE[i])";
1218
+
1219
+ // Keyed `<namespace>.<property>`, mirroring PROPERTY_TYPE_CORRECTIONS; a key
1220
+ // with an `engine` field is also an `EmitOptions.bindingEvidence` key.
1111
1221
  export const PROPERTY_KEY_RANGES: ReadonlyMap<string, PropertyKeyRange> = new Map([
1112
- ["model.textureN", { prefix: "texture", first: 0, last: 7, evidence: "textureN where N is 0-7" }],
1222
+ [
1223
+ "model.textureN",
1224
+ {
1225
+ prefix: "texture",
1226
+ first: 0,
1227
+ last: 7,
1228
+ evidence: "textureN where N is 0-7",
1229
+ engine: {
1230
+ last: 15,
1231
+ pins: [
1232
+ {
1233
+ file: "render/src/dmsdk/render/render.h",
1234
+ contains: "static const uint32_t MAX_TEXTURE_COUNT = 16;",
1235
+ },
1236
+ {
1237
+ file: "gamesys/src/gamesys/gamesys.cpp",
1238
+ contains: `const dmhash_t PROP_TEXTURE[dmRender::RenderObject::MAX_TEXTURE_COUNT] = { ${Array.from(
1239
+ { length: 16 },
1240
+ (_, unit) => `dmHashString64("texture${unit}"),`,
1241
+ ).join(" ")} };`,
1242
+ },
1243
+ {
1244
+ file: "gamesys/src/gamesys/components/comp_model.cpp",
1245
+ cFunction: "CompModelGetProperty",
1246
+ contains: MODEL_TEXTURE_LOOP,
1247
+ },
1248
+ {
1249
+ file: "gamesys/src/gamesys/components/comp_model.cpp",
1250
+ cFunction: "CompModelSetProperty",
1251
+ contains: MODEL_TEXTURE_LOOP,
1252
+ },
1253
+ ],
1254
+ },
1255
+ },
1256
+ ],
1113
1257
  ]);
1114
1258
 
1115
1259
  // A parameter the ref-doc metadata marks required while the same element's prose
@@ -1769,7 +1913,7 @@ export const CONSTANT_SLOT_RESOLUTIONS: ReadonlyMap<string, ConstantSlotResoluti
1769
1913
  ],
1770
1914
  ]);
1771
1915
 
1772
- export interface ConstantUnionAlias {
1916
+ export interface ConstantUnionNamedRow {
1773
1917
  // The namespace the alias is declared in. It is the namespace the union
1774
1918
  // *means*, which is not always the one its constants live in: the three
1775
1919
  // buffer keys `render.clear` accepts are `graphics.BUFFER_TYPE_*` constants,
@@ -1780,11 +1924,22 @@ export interface ConstantUnionAlias {
1780
1924
  readonly members: readonly string[];
1781
1925
  }
1782
1926
 
1783
- // A documented constant union gets a public name so a caller can spell the type
1784
- // their own wrapper takes. Identity is the *member set*, never a name prefix:
1785
- // `gui.ANCHOR_*` splits into two rows because the x and y slots accept
1786
- // different thirds of it, and `graphics.BUFFER_TYPE_*` splits because
1787
- // `render.clear` accepts three of the six.
1927
+ // A union that is a strict subset of a named row in the same home. It declares
1928
+ // nothing: each slot spells it `Exclude<<home>.<parent>, ...>`, so one namespace
1929
+ // never carries two names for overlapping sets.
1930
+ export interface ConstantUnionSubsetRow {
1931
+ readonly home: string;
1932
+ readonly parent: string;
1933
+ readonly members: readonly string[];
1934
+ }
1935
+
1936
+ export type ConstantUnionAlias = ConstantUnionNamedRow | ConstantUnionSubsetRow;
1937
+
1938
+ // A documented constant union gets a public spelling so a caller can write the
1939
+ // type their own wrapper takes. Identity is the *member set*, never a name
1940
+ // prefix: the x and y anchor slots accept different thirds of `gui.ANCHOR_*`,
1941
+ // so each is its own subset row of `gui.ANCHOR`, and `graphics.BUFFER_TYPE_*`
1942
+ // splits because `render.clear` accepts three of the six.
1788
1943
  //
1789
1944
  // The table is the only thing the emitter reads, and every name in it has been
1790
1945
  // reviewed: a longest-common-prefix rule proposes, a human disposes. A union
@@ -1794,7 +1949,7 @@ export interface ConstantUnionAlias {
1794
1949
  export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1795
1950
  {
1796
1951
  home: "buffer",
1797
- name: "ValueType",
1952
+ name: "VALUE_TYPE",
1798
1953
  members: [
1799
1954
  "buffer.VALUE_TYPE_FLOAT32",
1800
1955
  "buffer.VALUE_TYPE_INT16",
@@ -1809,7 +1964,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1809
1964
  },
1810
1965
  {
1811
1966
  home: "collectionfactory",
1812
- name: "Status",
1967
+ name: "STATUS",
1813
1968
  members: [
1814
1969
  "collectionfactory.STATUS_UNLOADED",
1815
1970
  "collectionfactory.STATUS_LOADING",
@@ -1818,12 +1973,12 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1818
1973
  },
1819
1974
  {
1820
1975
  home: "factory",
1821
- name: "Status",
1976
+ name: "STATUS",
1822
1977
  members: ["factory.STATUS_UNLOADED", "factory.STATUS_LOADING", "factory.STATUS_LOADED"],
1823
1978
  },
1824
1979
  {
1825
1980
  home: "go",
1826
- name: "Easing",
1981
+ name: "EASING",
1827
1982
  members: [
1828
1983
  "go.EASING_INBACK",
1829
1984
  "go.EASING_INBOUNCE",
@@ -1870,7 +2025,20 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1870
2025
  },
1871
2026
  {
1872
2027
  home: "go",
1873
- name: "Playback",
2028
+ name: "PLAYBACK",
2029
+ members: [
2030
+ "go.PLAYBACK_LOOP_BACKWARD",
2031
+ "go.PLAYBACK_LOOP_FORWARD",
2032
+ "go.PLAYBACK_LOOP_PINGPONG",
2033
+ "go.PLAYBACK_NONE",
2034
+ "go.PLAYBACK_ONCE_BACKWARD",
2035
+ "go.PLAYBACK_ONCE_FORWARD",
2036
+ "go.PLAYBACK_ONCE_PINGPONG",
2037
+ ],
2038
+ },
2039
+ {
2040
+ home: "go",
2041
+ parent: "PLAYBACK",
1874
2042
  members: [
1875
2043
  "go.PLAYBACK_ONCE_FORWARD",
1876
2044
  "go.PLAYBACK_ONCE_BACKWARD",
@@ -1884,7 +2052,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1884
2052
  // target defines the constants; only the newer ones name the union.
1885
2053
  {
1886
2054
  home: "graphics",
1887
- name: "BlendFactor",
2055
+ name: "BLEND_FACTOR",
1888
2056
  members: [
1889
2057
  "graphics.BLEND_FACTOR_ZERO",
1890
2058
  "graphics.BLEND_FACTOR_ONE",
@@ -1905,7 +2073,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1905
2073
  },
1906
2074
  {
1907
2075
  home: "graphics",
1908
- name: "BufferType",
2076
+ name: "BUFFER_TYPE",
1909
2077
  members: [
1910
2078
  "graphics.BUFFER_TYPE_COLOR0_BIT",
1911
2079
  "graphics.BUFFER_TYPE_COLOR1_BIT",
@@ -1917,7 +2085,22 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1917
2085
  },
1918
2086
  {
1919
2087
  home: "graphics",
1920
- name: "State",
2088
+ name: "STATE",
2089
+ members: [
2090
+ "graphics.STATE_ALPHA_TEST",
2091
+ "graphics.STATE_ALPHA_TEST_SUPPORTED",
2092
+ "graphics.STATE_BLEND",
2093
+ "graphics.STATE_CULL_FACE",
2094
+ "graphics.STATE_DEPTH_TEST",
2095
+ "graphics.STATE_POLYGON_OFFSET_FILL",
2096
+ "graphics.STATE_SCISSOR_TEST",
2097
+ "graphics.STATE_STENCIL_TEST",
2098
+ ],
2099
+ },
2100
+ // The six render_script.cpp:RenderScript_EnableState accepts.
2101
+ {
2102
+ home: "graphics",
2103
+ parent: "STATE",
1921
2104
  members: [
1922
2105
  "graphics.STATE_DEPTH_TEST",
1923
2106
  "graphics.STATE_STENCIL_TEST",
@@ -1929,12 +2112,33 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1929
2112
  },
1930
2113
  {
1931
2114
  home: "gui",
1932
- name: "AdjustMode",
2115
+ name: "ADJUST",
1933
2116
  members: ["gui.ADJUST_FIT", "gui.ADJUST_ZOOM", "gui.ADJUST_STRETCH"],
1934
2117
  },
1935
2118
  {
1936
2119
  home: "gui",
1937
- name: "BlendMode",
2120
+ name: "ANCHOR",
2121
+ members: [
2122
+ "gui.ANCHOR_BOTTOM",
2123
+ "gui.ANCHOR_LEFT",
2124
+ "gui.ANCHOR_NONE",
2125
+ "gui.ANCHOR_RIGHT",
2126
+ "gui.ANCHOR_TOP",
2127
+ ],
2128
+ },
2129
+ {
2130
+ home: "gui",
2131
+ parent: "ANCHOR",
2132
+ members: ["gui.ANCHOR_NONE", "gui.ANCHOR_LEFT", "gui.ANCHOR_RIGHT"],
2133
+ },
2134
+ {
2135
+ home: "gui",
2136
+ parent: "ANCHOR",
2137
+ members: ["gui.ANCHOR_NONE", "gui.ANCHOR_TOP", "gui.ANCHOR_BOTTOM"],
2138
+ },
2139
+ {
2140
+ home: "gui",
2141
+ name: "BLEND",
1938
2142
  members: [
1939
2143
  "gui.BLEND_ALPHA",
1940
2144
  "gui.BLEND_ADD",
@@ -1945,12 +2149,12 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1945
2149
  },
1946
2150
  {
1947
2151
  home: "gui",
1948
- name: "ClippingMode",
2152
+ name: "CLIPPING_MODE",
1949
2153
  members: ["gui.CLIPPING_MODE_NONE", "gui.CLIPPING_MODE_STENCIL"],
1950
2154
  },
1951
2155
  {
1952
2156
  home: "gui",
1953
- name: "Easing",
2157
+ name: "EASING",
1954
2158
  members: [
1955
2159
  "gui.EASING_INBACK",
1956
2160
  "gui.EASING_INBOUNCE",
@@ -1997,7 +2201,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
1997
2201
  },
1998
2202
  {
1999
2203
  home: "gui",
2000
- name: "KeyboardType",
2204
+ name: "KEYBOARD_TYPE",
2001
2205
  members: [
2002
2206
  "gui.KEYBOARD_TYPE_DEFAULT",
2003
2207
  "gui.KEYBOARD_TYPE_EMAIL",
@@ -2007,7 +2211,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2007
2211
  },
2008
2212
  {
2009
2213
  home: "gui",
2010
- name: "NodeType",
2214
+ name: "TYPE",
2011
2215
  members: [
2012
2216
  "gui.TYPE_BOX",
2013
2217
  "gui.TYPE_TEXT",
@@ -2018,12 +2222,12 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2018
2222
  },
2019
2223
  {
2020
2224
  home: "gui",
2021
- name: "PieBounds",
2225
+ name: "PIEBOUNDS",
2022
2226
  members: ["gui.PIEBOUNDS_RECTANGLE", "gui.PIEBOUNDS_ELLIPSE"],
2023
2227
  },
2024
2228
  {
2025
2229
  home: "gui",
2026
- name: "Pivot",
2230
+ name: "PIVOT",
2027
2231
  members: [
2028
2232
  "gui.PIVOT_CENTER",
2029
2233
  "gui.PIVOT_N",
@@ -2038,7 +2242,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2038
2242
  },
2039
2243
  {
2040
2244
  home: "gui",
2041
- name: "Playback",
2245
+ name: "PLAYBACK",
2042
2246
  members: [
2043
2247
  "gui.PLAYBACK_ONCE_FORWARD",
2044
2248
  "gui.PLAYBACK_ONCE_BACKWARD",
@@ -2050,7 +2254,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2050
2254
  },
2051
2255
  {
2052
2256
  home: "gui",
2053
- name: "Property",
2257
+ name: "PROP",
2054
2258
  members: [
2055
2259
  "gui.PROP_POSITION",
2056
2260
  "gui.PROP_ROTATION",
@@ -2069,7 +2273,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2069
2273
  },
2070
2274
  {
2071
2275
  home: "gui",
2072
- name: "SafeAreaMode",
2276
+ name: "SAFE_AREA",
2073
2277
  members: [
2074
2278
  "gui.SAFE_AREA_NONE",
2075
2279
  "gui.SAFE_AREA_LONG",
@@ -2079,22 +2283,12 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2079
2283
  },
2080
2284
  {
2081
2285
  home: "gui",
2082
- name: "SizeMode",
2286
+ name: "SIZE_MODE",
2083
2287
  members: ["gui.SIZE_MODE_MANUAL", "gui.SIZE_MODE_AUTO"],
2084
2288
  },
2085
- {
2086
- home: "gui",
2087
- name: "XAnchor",
2088
- members: ["gui.ANCHOR_NONE", "gui.ANCHOR_LEFT", "gui.ANCHOR_RIGHT"],
2089
- },
2090
- {
2091
- home: "gui",
2092
- name: "YAnchor",
2093
- members: ["gui.ANCHOR_NONE", "gui.ANCHOR_TOP", "gui.ANCHOR_BOTTOM"],
2094
- },
2095
2289
  {
2096
2290
  home: "profiler",
2097
- name: "Mode",
2291
+ name: "MODE",
2098
2292
  members: [
2099
2293
  "profiler.MODE_RUN",
2100
2294
  "profiler.MODE_PAUSE",
@@ -2104,7 +2298,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2104
2298
  },
2105
2299
  {
2106
2300
  home: "profiler",
2107
- name: "ViewMode",
2301
+ name: "VIEW_MODE",
2108
2302
  members: ["profiler.VIEW_MODE_FULL", "profiler.VIEW_MODE_MINIMIZED"],
2109
2303
  },
2110
2304
  {
@@ -2124,7 +2318,7 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2124
2318
  },
2125
2319
  {
2126
2320
  home: "sys",
2127
- name: "NetworkConnectivity",
2321
+ name: "NETWORK",
2128
2322
  members: [
2129
2323
  "sys.NETWORK_DISCONNECTED",
2130
2324
  "sys.NETWORK_CONNECTED_CELLULAR",
@@ -2133,12 +2327,12 @@ export const CONSTANT_UNION_ALIASES: readonly ConstantUnionAlias[] = [
2133
2327
  },
2134
2328
  {
2135
2329
  home: "window",
2136
- name: "DimModeState",
2330
+ name: "DIMMING",
2137
2331
  members: ["window.DIMMING_UNKNOWN", "window.DIMMING_ON", "window.DIMMING_OFF"],
2138
2332
  },
2139
2333
  {
2140
2334
  home: "window",
2141
- name: "DimModeStateSettable",
2335
+ parent: "DIMMING",
2142
2336
  members: ["window.DIMMING_ON", "window.DIMMING_OFF"],
2143
2337
  },
2144
2338
  ];
@@ -2158,11 +2352,45 @@ export function constantUnionAlias(fqns: readonly string[]): ConstantUnionAlias
2158
2352
  return CONSTANT_UNION_ALIAS_BY_MEMBERS.get(constantUnionAliasKey(fqns));
2159
2353
  }
2160
2354
 
2161
- // The alias declarations a module owns, in name order. A row is declared in its
2162
- // home whether or not that module references it, so a cross-namespace row is
2163
- // reachable from the module whose signatures spell it.
2164
- export function constantUnionAliasDeclarations(namespace: string): string[] {
2165
- return CONSTANT_UNION_ALIASES.filter((row) => row.home === namespace)
2355
+ // What a slot taking exactly the row's members is written as: a named row's
2356
+ // qualified name, or a subset row's parent minus the parent's other members,
2357
+ // listed in the parent's order.
2358
+ export function constantUnionSpelling(row: ConstantUnionAlias): string {
2359
+ if ("name" in row) return `${row.home}.${engineTypeName(row.name)}`;
2360
+ const parent = CONSTANT_UNION_ALIASES.find(
2361
+ (candidate): candidate is ConstantUnionNamedRow =>
2362
+ "name" in candidate && candidate.home === row.home && candidate.name === row.parent,
2363
+ );
2364
+ if (parent === undefined) {
2365
+ throw new Error(`constant union subset of ${row.home}.${row.parent}: no such named row`);
2366
+ }
2367
+ const kept = new Set(row.members);
2368
+ const excluded = parent.members.filter((member) => !kept.has(member));
2369
+ return `Exclude<${row.home}.${engineTypeName(row.parent)}, ${excluded.map((m) => `typeof ${m}`).join(" | ")}>`;
2370
+ }
2371
+
2372
+ // The named rows a module owns; a subset row declares nothing. A row is
2373
+ // declared in its home whether or not that module references it, so a
2374
+ // cross-namespace row is reachable from the module whose signatures spell it.
2375
+ // A row whose name the module's own `ENUM` already declares is left to that
2376
+ // `ENUM`, which carries the reference's member order and nilable arms.
2377
+ function ownedConstantUnionAliases(
2378
+ namespace: string,
2379
+ enumDeclared: ReadonlySet<string>,
2380
+ ): ConstantUnionNamedRow[] {
2381
+ return CONSTANT_UNION_ALIASES.filter(
2382
+ (row): row is ConstantUnionNamedRow =>
2383
+ "name" in row && row.home === namespace && !enumDeclared.has(row.name),
2384
+ );
2385
+ }
2386
+
2387
+ // The alias declarations a module owns, in name order.
2388
+ export function constantUnionAliasDeclarations(
2389
+ namespace: string,
2390
+ enumDeclared: ReadonlySet<string> = new Set(),
2391
+ ): string[] {
2392
+ return ownedConstantUnionAliases(namespace, enumDeclared)
2393
+ .map((row) => ({ name: engineTypeName(row.name), members: row.members }))
2166
2394
  .sort((a, b) => a.name.localeCompare(b.name))
2167
2395
  .map((row) => unionAliasDeclaration(row.name, row.members));
2168
2396
  }
@@ -2178,10 +2406,12 @@ function unionAliasDeclaration(
2178
2406
  }
2179
2407
 
2180
2408
  // A slot of a function the authored-overload lane declares, published under
2181
- // one alias on every target so the version-independent overload file can
2182
- // name the type each target's own reference gives that slot.
2409
+ // one name on every target so the version-independent overload file can name
2410
+ // the type each target's own reference gives that slot. The name is the
2411
+ // PascalCase name of the newest reference's own declaration for the slot; a
2412
+ // target that declares it emits no alias.
2183
2413
  export const OVERLOAD_SLOT_ALIASES: ReadonlyMap<string, string> = new Map([
2184
- ["render.render_target:param:parameters", "RenderTargetParams"],
2414
+ ["render.render_target:param:parameters", engineTypeName("render_target_params")],
2185
2415
  ]);
2186
2416
 
2187
2417
  const DOC_CONSTANT_TOKEN =
@@ -2395,7 +2625,34 @@ export type TableSlotCuration = (
2395
2625
  | { kind: "verbatim"; ts: string }
2396
2626
  ) & { readonly retypedUpstream?: readonly string[] };
2397
2627
 
2398
- export const SOCKET_HANDLE_TOKENS = ["client", "master", "unconnected"] as const;
2628
+ // The interface a module's component properties are declared on; its PROPERTY
2629
+ // entries carry its members, so it signs no TYPEDEF of its own.
2630
+ export const PROPERTIES_INTERFACE = engineTypeName("properties");
2631
+
2632
+ // The ref-doc tokens of the handles `socket.select` takes and returns, in their
2633
+ // 1.13.2 TYPEDEF spelling, which the type map resolves to each handle's interface.
2634
+ export const SOCKET_HANDLE_TOKENS = (["client", "master", "unconnected"] as const).map(
2635
+ (receiver) => `socket_${receiver}`,
2636
+ );
2637
+
2638
+ // The interface a module's `<receiver>:<method>` colon methods are emitted on:
2639
+ // the socket handles take their `Socket<Receiver>` name, any other receiver
2640
+ // keeps its own.
2641
+ export function handleInterfaceName(namespace: string, receiver: string): string {
2642
+ return namespace === "socket" && Object.hasOwn(SOCKET_HANDLE_NAMES, receiver)
2643
+ ? SOCKET_HANDLE_NAMES[receiver as SocketHandleReceiver]
2644
+ : receiver;
2645
+ }
2646
+
2647
+ // The typedef names a module's handle interfaces stand for, which emit as the
2648
+ // interface rather than an alias: each receiver (1.13.1 `client`) and each
2649
+ // interface name, which the 1.13.2 `socket_client` takes once renamed
2650
+ // (`SocketClient`).
2651
+ function handleTypedefNames(namespace: string, receivers: Iterable<string>): Set<string> {
2652
+ return new Set(
2653
+ [...receivers].flatMap((receiver) => [receiver, handleInterfaceName(namespace, receiver)]),
2654
+ );
2655
+ }
2399
2656
 
2400
2657
  // Shared Box2D (b2d.*) table shapes, curated from the ref-doc prose (the docs
2401
2658
  // name fields in running text, not a machine-readable `<dl>`). Reused across the
@@ -3390,7 +3647,7 @@ export const CALLBACK_SIGNATURE_CURATIONS: ReadonlyMap<string, CallbackSignature
3390
3647
  "(self: unknown, event: (typeof window)[Extract<keyof typeof window, `WINDOW_EVENT_${string}`>], data: Record<string | number, unknown>) => void",
3391
3648
  typed:
3392
3649
  // biome-ignore lint/suspicious/noTemplateCurlyInString: emitted verbatim as a TS template literal type
3393
- "(self: unknown, event: (typeof window)[Extract<keyof typeof window, `WINDOW_EVENT_${string}`>], data: window.event_data) => void",
3650
+ "(self: unknown, event: (typeof window)[Extract<keyof typeof window, `WINDOW_EVENT_${string}`>], data: window.EventData) => void",
3394
3651
  },
3395
3652
  ],
3396
3653
  [
@@ -3458,9 +3715,11 @@ export const NESTED_FIELD_CURATIONS: ReadonlyMap<string, readonly TableField[]>
3458
3715
  // `<element>:<kind>:<slot>:<field>`, mirroring NESTED_FIELD_CURATIONS; a field
3459
3716
  // one level down is `<field>.<member>`, or `<field>[].<member>` inside a list.
3460
3717
  //
3461
- // The atlas animation `playback` field is documented as `constant` with the
3462
- // prose "the default value is go.PLAYBACK_ONCE_FORWARD", which names the enum
3463
- // the bare token cannot.
3718
+ // Unpinned records below rest on ref-doc tokens or prose. The atlas animation
3719
+ // `playback` field is documented as `constant` with the prose "the default value
3720
+ // is go.PLAYBACK_ONCE_FORWARD", which names the enum the bare token cannot; its
3721
+ // records also carry engine binding pins and apply only when the matching
3722
+ // `EmitOptions.bindingEvidence` key holds for the target.
3464
3723
  //
3465
3724
  // `gui.set`'s `options.key` is documented as `hash`, while upstream's own
3466
3725
  // example sets a named font with `{ key = "my_font_name" }`.
@@ -3473,21 +3732,47 @@ export const NESTED_FIELD_CURATIONS: ReadonlyMap<string, readonly TableField[]>
3473
3732
  // script_resource.cpp:CreateSoundData reads it with CheckBufferOrString.
3474
3733
  const CONSTANT_VALUE_TS = "Vector4 | Vector3 | Matrix4 | number | (Vector4 | Matrix4)[]";
3475
3734
  const ATTRIBUTE_VALUE_TS = "Vector4 | Vector3 | Matrix4 | number | number[]";
3476
- export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
3477
- ["compute.set_constants:param:constants:value", CONSTANT_VALUE_TS],
3478
- ["material.set_constants:param:constants:value", CONSTANT_VALUE_TS],
3479
- ["material.set_vertex_attributes:param:attributes:value", ATTRIBUTE_VALUE_TS],
3480
- ["material.get_vertex_attributes:return:table:value", ATTRIBUTE_VALUE_TS],
3481
- ["resource.set_atlas:param:table:animations[].playback", "go.Playback"],
3482
- ["resource.create_atlas:param:table:animations[].playback", "go.Playback"],
3483
- ["resource.get_atlas:return:data:animations[].playback", "go.Playback"],
3484
- ["render.set_render_target:param:options:transient", "graphics.BufferType[]"],
3485
- ["resource.create_sound_data:param:options:data", 'string | Opaque<"buffer">'],
3735
+ const ATLAS_BINDING_FILE = "gamesys/src/gamesys/scripts/script_resource.cpp";
3736
+ const ATLAS_INPUT_PLAYBACK_BINDING: BindingPin = {
3737
+ file: ATLAS_BINDING_FILE,
3738
+ cFunction: "MakeTextureSetFromLua",
3739
+ contains: "GameObjectPlaybackToDDFPlayback((dmGameObject::Playback) lua_tointeger(L,-1))",
3740
+ };
3741
+ const ATLAS_OUTPUT_PLAYBACK_BINDING: BindingPin = {
3742
+ file: ATLAS_BINDING_FILE,
3743
+ cFunction: "GetAtlas",
3744
+ contains: "(lua_Integer) DDFPlaybackToGameObjectPlayback(anim.m_Playback)",
3745
+ };
3746
+
3747
+ export interface TableFieldTypeOverride {
3748
+ readonly ts: string;
3749
+ readonly binding?: BindingPin;
3750
+ }
3751
+
3752
+ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, TableFieldTypeOverride> = new Map([
3753
+ ["compute.set_constants:param:constants:value", { ts: CONSTANT_VALUE_TS }],
3754
+ ["material.set_constants:param:constants:value", { ts: CONSTANT_VALUE_TS }],
3755
+ ["material.set_vertex_attributes:param:attributes:value", { ts: ATTRIBUTE_VALUE_TS }],
3756
+ ["material.get_vertex_attributes:return:table:value", { ts: ATTRIBUTE_VALUE_TS }],
3757
+ [
3758
+ "resource.set_atlas:param:table:animations[].playback",
3759
+ { ts: "go.Playback", binding: ATLAS_INPUT_PLAYBACK_BINDING },
3760
+ ],
3761
+ [
3762
+ "resource.create_atlas:param:table:animations[].playback",
3763
+ { ts: "go.Playback", binding: ATLAS_INPUT_PLAYBACK_BINDING },
3764
+ ],
3765
+ [
3766
+ "resource.get_atlas:return:data:animations[].playback",
3767
+ { ts: "go.Playback", binding: ATLAS_OUTPUT_PLAYBACK_BINDING },
3768
+ ],
3769
+ ["render.set_render_target:param:options:transient", { ts: "graphics.BufferType[]" }],
3770
+ ["resource.create_sound_data:param:options:data", { ts: 'string | Opaque<"buffer">' }],
3486
3771
  // The option bags 1.13.2 types as a STRUCT, whose members still carry the
3487
3772
  // narrower upstream types: `gui.set`'s (its slot is declared in
3488
3773
  // gui-overloads.d.ts) and `resource.create_sound_data`'s.
3489
- ["gui.set_options:member:key", "string | Hash"],
3490
- ["resource.sound_data_options:member:data", 'string | Opaque<"buffer">'],
3774
+ ["gui.set_options:member:key", { ts: "string | Hash" }],
3775
+ ["resource.sound_data_options:member:data", { ts: 'string | Opaque<"buffer">' }],
3491
3776
  // Bitmasks typed as their flag enum: the create_texture example passes
3492
3777
  // TEXTURE_USAGE_FLAG_STORAGE + TEXTURE_USAGE_FLAG_SAMPLE, a sum no single member
3493
3778
  // names, and a reported value combines them the same way.
@@ -3498,13 +3783,15 @@ export const TABLE_FIELD_TYPE_OVERRIDES: ReadonlyMap<string, string> = new Map([
3498
3783
  "resource.render_target_attachment_info",
3499
3784
  "material.texture_info",
3500
3785
  ] as const
3501
- ).map((struct) => [`${struct}:member:flags`, "graphics.TEXTURE_USAGE_FLAG | number"] as const),
3502
- ["render.render_target_buffer_params:member:flags", "render.RENDER_TARGET_FLAG | number"],
3786
+ ).map(
3787
+ (struct) => [`${struct}:member:flags`, { ts: "graphics.TextureUsageFlag | number" }] as const,
3788
+ ),
3789
+ ["render.render_target_buffer_params:member:flags", { ts: "render.RenderTargetFlag | number" }],
3503
3790
  // gamesys_script.cpp:PushSampler, behind both get_samplers, pushes the raw
3504
3791
  // texture type; NormalizeBindableTextureType never runs there. A separate
3505
3792
  // sampler reports TEXTURE_TYPE_SAMPLER (6) and a separate texture binding one
3506
3793
  // of the four TEXTURE_TYPE_TEXTURE_* (7-10), none of which has a Lua constant.
3507
- ["material.sampler_info:member:type", "graphics.TEXTURE_TYPE | 6 | 7 | 8 | 9 | 10"],
3794
+ ["material.sampler_info:member:type", { ts: "graphics.TextureType | 6 | 7 | 8 | 9 | 10" }],
3508
3795
  ]);
3509
3796
 
3510
3797
  // A field the engine accepts on an option bag whose `<dl>` upstream otherwise
@@ -3935,12 +4222,16 @@ export function applyFieldTypeOverrides(
3935
4222
  slotKind: "param" | "return" | undefined,
3936
4223
  slotName: string | undefined,
3937
4224
  fields: readonly TableField[],
4225
+ bindingEvidence: ReadonlySet<string>,
3938
4226
  ): TableField[] {
3939
4227
  if (slotKind === undefined || slotName === undefined) return [...fields];
3940
4228
  const prefix = `${tableSlotKey(elementName, slotKind, slotName)}:`;
3941
4229
  const typed = (field: TableField, path: string): TableField => {
3942
- const override = TABLE_FIELD_TYPE_OVERRIDES.get(`${prefix}${path}`);
3943
- return override === undefined ? field : { ...field, tsType: override };
4230
+ const key = `${prefix}${path}`;
4231
+ const override = TABLE_FIELD_TYPE_OVERRIDES.get(key);
4232
+ return override === undefined || (override.binding !== undefined && !bindingEvidence.has(key))
4233
+ ? field
4234
+ : { ...field, tsType: override.ts };
3944
4235
  };
3945
4236
  return fields.map((field) => {
3946
4237
  const own = typed(field, field.name);
@@ -4315,7 +4606,7 @@ export const TYPEDEF_ALIAS_CORRECTIONS: ReadonlyMap<string, TypedefAliasCorrecti
4315
4606
  upstream: [
4316
4607
  "{ type?:graphics.TEXTURE_TYPE, sample_count?:integer, [graphics.BUFFER_TYPE]:render.render_target_buffer_params }",
4317
4608
  ],
4318
- ts: "{ type?: render.RenderTargetTextureType; sample_count?: number } & { [key: number]: render.render_target_buffer_params }",
4609
+ ts: "{ type?: render.RenderTargetTextureType; sample_count?: number } & { [key: number]: render.RenderTargetBufferParams }",
4319
4610
  reason:
4320
4611
  'render_script.cpp:RenderScript_RenderTarget raises "Invalid render target texture type" on any `type` but graphics.TEXTURE_TYPE_2D and graphics.TEXTURE_TYPE_CUBE_MAP',
4321
4612
  },
@@ -4344,15 +4635,17 @@ export const TYPEDEF_ALIAS_CORRECTIONS: ReadonlyMap<string, TypedefAliasCorrecti
4344
4635
  // function yields.
4345
4636
  //
4346
4637
  // An engine typedef that names its aliased type: a `userdata`/`number` handle the
4347
- // type map resolves (`vector`, `socket_client`) aliases that mapping, an
4638
+ // type map resolves (`timer_handle`, `buffer_data`) aliases that mapping, an
4348
4639
  // unresolved one keeps the brand, and any other aliased type (a union, literals, a
4349
- // record) aliases the type itself.
4640
+ // record) aliases the type itself. A handle the type map resolves to a core
4641
+ // global (`vector` -> `Vector`, `resource_data` -> `Hash`) declares nothing, since
4642
+ // its slots already name the global: `null`.
4350
4643
  function typedefAlias(
4351
4644
  typedef: ApiTypedef,
4352
4645
  local: string,
4353
4646
  rawMapType: (token: string) => string,
4354
4647
  mapType: DefoldTypeMapper,
4355
- ): string {
4648
+ ): string | null {
4356
4649
  const brand = `Opaque<"${typedef.name}">`;
4357
4650
  const aliasOf = typedef.aliasOf ?? [];
4358
4651
  const correction = TYPEDEF_ALIAS_CORRECTIONS.get(typedef.name);
@@ -4371,10 +4664,23 @@ function typedefAlias(
4371
4664
  }
4372
4665
  const mapped = rawMapType(typedef.name);
4373
4666
  if (mapped.startsWith(brand)) return `type ${local} = ${mapped};`;
4667
+ if (aliasOf.length > 0 && CORE_GLOBAL_TYPES.has(mapped)) return null;
4374
4668
  const resolved = aliasOf.length > 0 && mapped !== "unknown" && mapped !== local;
4375
4669
  return `type ${local} = ${resolved ? mapped : brand};`;
4376
4670
  }
4377
4671
 
4672
+ // The global types `core-types.ts` declares that the engine vocabulary maps a
4673
+ // handle to.
4674
+ const CORE_GLOBAL_TYPES: ReadonlySet<string> = new Set([
4675
+ "Hash",
4676
+ "Matrix4",
4677
+ "Quaternion",
4678
+ "Url",
4679
+ "Vector",
4680
+ "Vector3",
4681
+ "Vector4",
4682
+ ]);
4683
+
4378
4684
  function isHandleAlias(aliasOf: readonly string[]): boolean {
4379
4685
  return aliasOf.length === 1 && (aliasOf[0] === "userdata" || aliasOf[0] === "number");
4380
4686
  }
@@ -4391,8 +4697,9 @@ interface TypeDeclaration {
4391
4697
 
4392
4698
  // A typedef with no aliased type keeps the flat-name rule it always had; one that
4393
4699
  // names its aliased type, and every struct, sits at its name less the module
4394
- // prefix, nested when that local is dotted. Typedefs come before structs, each in
4395
- // document order.
4700
+ // prefix, nested when that local is dotted. Every leaf goes through
4701
+ // `engineTypeName`, and `skip` matches a typedef by its reference local or its
4702
+ // renamed path. Typedefs come before structs, each in document order.
4396
4703
  function collectTypeDeclarations(
4397
4704
  module: ApiModule,
4398
4705
  skip: ReadonlySet<string>,
@@ -4409,12 +4716,15 @@ function collectTypeDeclarations(
4409
4716
  for (const t of module.typedefs) {
4410
4717
  const local =
4411
4718
  t.aliasOf === undefined ? (TS_IDENTIFIER.test(t.name) ? t.name : null) : localOf(t.name);
4412
- if (local === null || skip.has(local)) continue;
4413
- const name = local.slice(local.lastIndexOf(".") + 1);
4719
+ if (local === null) continue;
4720
+ const path = renamedLeaf(local);
4721
+ if (skip.has(local) || skip.has(path)) continue;
4722
+ const name = path.slice(path.lastIndexOf(".") + 1);
4414
4723
  const signature = typedefAlias(t, name, rawMapType, mapType);
4724
+ if (signature === null) continue;
4415
4725
  out.push({
4416
4726
  name,
4417
- path: local,
4727
+ path,
4418
4728
  lines: (indent, decl) => [`${indent}${decl}${signature}`],
4419
4729
  signature,
4420
4730
  });
@@ -4472,11 +4782,12 @@ function correctedListMemberType(
4472
4782
 
4473
4783
  function structDeclaration(
4474
4784
  struct: ApiStruct,
4475
- path: string,
4785
+ local: string,
4476
4786
  mapType: DefoldTypeMapper,
4477
4787
  structs: readonly ApiStruct[],
4478
4788
  bindingEvidence: ReadonlySet<string>,
4479
4789
  ): TypeDeclaration {
4790
+ const path = renamedLeaf(local);
4480
4791
  const name = path.slice(path.lastIndexOf(".") + 1);
4481
4792
  const memberPrefix = memberCorrectionKey(struct.name, "");
4482
4793
  const declared = new Set(struct.members.map((member) => member.name));
@@ -4503,7 +4814,9 @@ function structDeclaration(
4503
4814
  const members = [...struct.members, ...added].map((member) => {
4504
4815
  const key = memberCorrectionKey(struct.name, member.name);
4505
4816
  const list = LIST_FIELD_CORRECTIONS.get(key);
4506
- const retyped = TABLE_FIELD_TYPE_OVERRIDES.get(key);
4817
+ const listedRetype = TABLE_FIELD_TYPE_OVERRIDES.get(key);
4818
+ const retyped =
4819
+ listedRetype?.binding === undefined || bindingEvidence.has(key) ? listedRetype : undefined;
4507
4820
  const optional =
4508
4821
  (member.isOptional || OPTIONAL_MEMBER_CORRECTIONS.has(key)) &&
4509
4822
  !REQUIRED_FIELD_CORRECTIONS.has(key);
@@ -4511,7 +4824,7 @@ function structDeclaration(
4511
4824
  list !== undefined
4512
4825
  ? correctedListMemberType(struct, member, list, structs, mapType)
4513
4826
  : retyped !== undefined
4514
- ? retyped
4827
+ ? retyped.ts
4515
4828
  : member.type === ""
4516
4829
  ? "unknown"
4517
4830
  : mapType(member.type);
@@ -4535,7 +4848,10 @@ function structDeclaration(
4535
4848
  `${indent}}`,
4536
4849
  ];
4537
4850
  },
4538
- signature: `interface ${name} { ${members.map((member) => `${member.line} `).join("")}}`,
4851
+ signature: interfaceSignature(
4852
+ name,
4853
+ members.map((member) => member.line),
4854
+ ),
4539
4855
  };
4540
4856
  }
4541
4857
 
@@ -4594,8 +4910,9 @@ function moduleTypeMappers(
4594
4910
  * dropped, so the home's own copy wins; a type sharing a value's name
4595
4911
  * (`resource.atlas`) is untouched. A function moves the same way (1.13.2 files
4596
4912
  * `bullet3d.collision_object.get_shapes` in the shape doc), since the emitter
4597
- * declares only the functions under a module's own namespace. Modules with
4598
- * nothing moved in or out are returned as-is.
4913
+ * declares only the functions under a module's own namespace. A moved type
4914
+ * records the module that declared it as `declaredIn`. Modules with nothing
4915
+ * moved in or out are returned as-is.
4599
4916
  */
4600
4917
  export function routeTypeDeclarations(modules: readonly ApiModule[]): ApiModule[] {
4601
4918
  const homeOf = (name: string, declaring: string): string => {
@@ -4625,14 +4942,14 @@ export function routeTypeDeclarations(modules: readonly ApiModule[]): ApiModule[
4625
4942
  const structs = (module.structs ?? []).filter((struct) => {
4626
4943
  const home = homeOf(struct.name, module.namespace);
4627
4944
  if (home === module.namespace) return true;
4628
- into(home).structs.push(struct);
4945
+ into(home).structs.push({ ...struct, declaredIn: module.namespace });
4629
4946
  return false;
4630
4947
  });
4631
4948
  const typedefs = module.typedefs.filter((typedef) => {
4632
4949
  if (typedef.aliasOf === undefined) return true;
4633
4950
  const home = homeOf(typedef.name, module.namespace);
4634
4951
  if (home === module.namespace) return true;
4635
- into(home).typedefs.push(typedef);
4952
+ into(home).typedefs.push({ ...typedef, declaredIn: module.namespace });
4636
4953
  return false;
4637
4954
  });
4638
4955
  const functions = module.functions.filter((fn) => {
@@ -4675,27 +4992,32 @@ export function routeTypeDeclarations(modules: readonly ApiModule[]): ApiModule[
4675
4992
  /**
4676
4993
  * The TS path of every `ENUM`, `STRUCT` and aliasing `TYPEDEF` the (routed)
4677
4994
  * modules declare, keyed by its Lua name: the name itself when it sits under its
4678
- * module's namespace, else that name nested under the module
4679
- * (`on_input.action` in `go` is `go.on_input.action`).
4995
+ * module's namespace, else that name nested under the module, with its leaf
4996
+ * through `engineTypeName` (`on_input.action` in `go` is `go.on_input.Action`).
4680
4997
  */
4681
4998
  export function declaredTypePaths(modules: readonly ApiModule[]): Map<string, string> {
4682
4999
  const paths = new Map<string, string>();
4683
5000
  for (const module of modules) {
4684
- const names = [
4685
- ...(module.enums ?? []).map((e) => e.name),
4686
- ...(module.structs ?? []).map((struct) => struct.name),
4687
- ...module.typedefs.filter((t) => t.aliasOf !== undefined).map((t) => t.name),
4688
- ];
4689
- for (const name of names) {
4690
- paths.set(
4691
- name,
4692
- name.startsWith(`${module.namespace}.`) ? name : `${module.namespace}.${name}`,
4693
- );
5001
+ const qualified = (name: string): string =>
5002
+ name.startsWith(`${module.namespace}.`) ? name : `${module.namespace}.${name}`;
5003
+ for (const { name } of [
5004
+ ...(module.enums ?? []),
5005
+ ...(module.structs ?? []),
5006
+ ...module.typedefs.filter((t) => t.aliasOf !== undefined),
5007
+ ]) {
5008
+ paths.set(name, renamedLeaf(qualified(name)));
4694
5009
  }
4695
5010
  }
4696
5011
  return paths;
4697
5012
  }
4698
5013
 
5014
+ // A dotted type path with its last segment through `engineTypeName`: the
5015
+ // namespace segments mirror the engine's functions and stay as they are.
5016
+ function renamedLeaf(path: string): string {
5017
+ const dot = path.lastIndexOf(".");
5018
+ return `${path.slice(0, dot + 1)}${engineTypeName(path.slice(dot + 1))}`;
5019
+ }
5020
+
4699
5021
  export function emitDeclarations(documented: ApiModule, options?: EmitOptions): string {
4700
5022
  const module = withUndocumentedConstants(documented, options?.registeredConstants);
4701
5023
  const prefix = `${module.namespace}.`;
@@ -4744,6 +5066,13 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
4744
5066
  const nestedRoot = collectNestedGroups(module, prefix);
4745
5067
  const enumAliases = collectEnumAliases(module, constants, nestedRoot);
4746
5068
 
5069
+ const resolver = buildTableDocResolver(
5070
+ module.functions.map((fn) => ({
5071
+ name: fn.name,
5072
+ slots: [...fn.parameters, ...fn.returnValues],
5073
+ })),
5074
+ );
5075
+
4747
5076
  // Colon methods (`client:send`) are FUNCTION elements named `<receiver>:<method>`
4748
5077
  // and are NOT namespace-prefixed, so they fail the flat-identifier test in
4749
5078
  // prepareFunction (the colon) and are absent from `functions`. A returned handle
@@ -4751,27 +5080,26 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
4751
5080
  // interface, the same non-flat recovery the dns nested-namespace pass does one
4752
5081
  // container shape shallower.
4753
5082
  const handleGroups = collectHandleMethodGroups(module);
4754
- const handleReceivers = [...handleGroups.keys()].sort((a, b) => a.localeCompare(b));
4755
-
4756
- const resolver = buildTableDocResolver(
4757
- module.functions.map((fn) => ({
4758
- name: fn.name,
4759
- slots: [...fn.parameters, ...fn.returnValues],
4760
- })),
5083
+ const handleInterfaces = collectHandleInterfaces(
5084
+ module.namespace,
5085
+ handleGroups,
5086
+ mapType,
5087
+ resolver,
5088
+ constantTokens,
5089
+ urlParameters,
5090
+ bindingEvidence,
4761
5091
  );
4762
5092
 
4763
5093
  // A typedef that also has colon methods is emitted as a method-bearing
4764
5094
  // interface below, not an opaque brand alias.
4765
- const typeDeclarations = placeTypeDeclarations(
4766
- collectTypeDeclarations(
4767
- module,
4768
- new Set(handleGroups.keys()),
4769
- rawMapType,
4770
- mapType,
4771
- bindingEvidence,
4772
- ),
4773
- nestedRoot,
5095
+ const collectedTypes = collectTypeDeclarations(
5096
+ module,
5097
+ handleTypedefNames(module.namespace, handleGroups.keys()),
5098
+ rawMapType,
5099
+ mapType,
5100
+ bindingEvidence,
4774
5101
  );
5102
+ const typeDeclarations = placeTypeDeclarations(collectedTypes, nestedRoot);
4775
5103
 
4776
5104
  // A re-export alias (`export { _x as x }`) switches the ambient namespace out
4777
5105
  // of its implicit-export mode, so once any alias is present every sibling
@@ -4788,18 +5116,22 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
4788
5116
  lines.push(`declare namespace ${module.namespace} {`);
4789
5117
 
4790
5118
  for (const declaration of typeDeclarations) lines.push(...declaration.lines(INDENT, decl));
4791
- for (const aliasDecl of constantUnionAliasDeclarations(module.namespace)) {
5119
+ for (const aliasDecl of constantUnionAliasDeclarations(
5120
+ module.namespace,
5121
+ new Set(enumAliases.all.map((alias) => alias.path)),
5122
+ )) {
4792
5123
  lines.push(`${INDENT}${decl}${aliasDecl}`);
4793
5124
  }
4794
- for (const aliasDecl of overloadSlotAliasDeclarations(
5125
+ for (const { declaration } of overloadSlotAliasDeclarations(
4795
5126
  [...module.functions, ...(options?.withheldFunctions ?? [])],
5127
+ collectedTypes,
4796
5128
  prefix,
4797
5129
  mapType,
4798
5130
  constantTokens,
4799
5131
  urlParameters,
4800
5132
  bindingEvidence,
4801
5133
  )) {
4802
- lines.push(`${INDENT}${decl}${aliasDecl}`);
5134
+ lines.push(`${INDENT}${decl}${declaration}`);
4803
5135
  }
4804
5136
  for (const alias of enumAliases.topLevel) {
4805
5137
  lines.push(
@@ -4807,16 +5139,13 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
4807
5139
  );
4808
5140
  }
4809
5141
  const handleIndent = `${INDENT}${INDENT}`;
4810
- for (const receiver of handleReceivers) {
4811
- const group = handleGroups.get(receiver) ?? [];
4812
- lines.push(`${INDENT}${decl}interface ${receiver} {`);
4813
- for (const fn of group) {
5142
+ for (const { name, members } of handleInterfaces) {
5143
+ lines.push(`${INDENT}${decl}interface ${name} {`);
5144
+ for (const { fn, text } of members) {
4814
5145
  for (const docLine of functionDocLines(fn.original, translations, baseNotes, handleIndent)) {
4815
5146
  lines.push(docLine);
4816
5147
  }
4817
- lines.push(
4818
- `${handleIndent}${emitMethod(fn, mapType, resolver, constantTokens, urlParameters, bindingEvidence)}`,
4819
- );
5148
+ lines.push(`${handleIndent}${text}`);
4820
5149
  }
4821
5150
  lines.push(`${INDENT}}`);
4822
5151
  }
@@ -4962,9 +5291,9 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
4962
5291
 
4963
5292
  if (module.properties.length > 0) {
4964
5293
  const members = [...module.properties].sort((a, b) => a.name.localeCompare(b.name));
4965
- lines.push(`${INDENT}${decl}interface properties {`);
5294
+ lines.push(`${INDENT}${decl}interface ${PROPERTIES_INTERFACE} {`);
4966
5295
  for (const p of members) {
4967
- for (const member of emitPropertyMembers(p, mapType, module.namespace)) {
5296
+ for (const member of emitPropertyMembers(p, mapType, module.namespace, bindingEvidence)) {
4968
5297
  for (const docLine of summaryDocLines(p.brief, p.description, `${INDENT}${INDENT}`)) {
4969
5298
  lines.push(docLine);
4970
5299
  }
@@ -4979,17 +5308,25 @@ export function emitDeclarations(documented: ApiModule, options?: EmitOptions):
4979
5308
  }
4980
5309
 
4981
5310
  // One `type <alias> = <slot type>;` per `OVERLOAD_SLOT_ALIASES` slot these
4982
- // functions carry, in alias order. The type is the one `emitFunction` reports
4983
- // for the slot, so it reads as the function's own signature would. The
4984
- // resolver spans these functions alone, leaving the module's own untouched.
5311
+ // functions carry, in alias order, skipping an alias one of the module's
5312
+ // top-level type `declarations` already names. The type is the one
5313
+ // `emitFunction` reports for the slot, so it reads as the function's own
5314
+ // signature would. The resolver spans these functions alone, leaving the
5315
+ // module's own untouched.
4985
5316
  function overloadSlotAliasDeclarations(
4986
5317
  functions: readonly ApiFunction[],
5318
+ declarations: readonly TypeDeclaration[],
4987
5319
  prefix: string,
4988
5320
  mapType: DefoldTypeMapper,
4989
5321
  constantTokens: ConstantSlotTokens,
4990
5322
  urlParameters: UrlParameterTable,
4991
5323
  bindingEvidence: ReadonlySet<string>,
4992
- ): string[] {
5324
+ ): { name: string; declaration: string }[] {
5325
+ const declared = new Set(
5326
+ declarations
5327
+ .filter((declaration) => declaration.path === declaration.name)
5328
+ .map((declaration) => declaration.name),
5329
+ );
4993
5330
  const resolver = buildTableDocResolver(
4994
5331
  functions.map((fn) => ({ name: fn.name, slots: [...fn.parameters, ...fn.returnValues] })),
4995
5332
  );
@@ -4997,7 +5334,7 @@ function overloadSlotAliasDeclarations(
4997
5334
  for (const fn of functions) {
4998
5335
  for (const [position, p] of fn.parameters.entries()) {
4999
5336
  const alias = OVERLOAD_SLOT_ALIASES.get(tableSlotKey(fn.name, "param", p.name));
5000
- if (alias === undefined) continue;
5337
+ if (alias === undefined || declared.has(alias)) continue;
5001
5338
  const prepared = prepareFunction(fn, prefix);
5002
5339
  if (prepared === null) continue;
5003
5340
  const slots: Record<string, SlotType> = {};
@@ -5024,7 +5361,51 @@ function overloadSlotAliasDeclarations(
5024
5361
  }
5025
5362
  return [...types]
5026
5363
  .sort(([a], [b]) => a.localeCompare(b))
5027
- .map(([alias, type]) => `type ${alias} = ${type};`);
5364
+ .map(([name, type]) => ({ name, declaration: `type ${name} = ${type};` }));
5365
+ }
5366
+
5367
+ // A method of a handle interface: the member text the declarations print inside
5368
+ // the interface, which its FUNCTION entry signs too.
5369
+ interface HandleMember {
5370
+ fn: PreparedFunction;
5371
+ text: string;
5372
+ slotTypes: SlotTypes;
5373
+ }
5374
+
5375
+ // Each handle interface a module's colon methods declare, sorted by name, with
5376
+ // its methods in group order.
5377
+ function collectHandleInterfaces(
5378
+ namespace: string,
5379
+ handleGroups: ReadonlyMap<string, readonly PreparedFunction[]>,
5380
+ mapType: DefoldTypeMapper,
5381
+ resolver: TableDocResolver,
5382
+ constantTokens: ConstantSlotTokens,
5383
+ urlParameters: UrlParameterTable,
5384
+ bindingEvidence: ReadonlySet<string>,
5385
+ ): { name: string; members: HandleMember[] }[] {
5386
+ return [...handleGroups]
5387
+ .map(([receiver, group]) => ({
5388
+ name: handleInterfaceName(namespace, receiver),
5389
+ members: group.map((fn): HandleMember => {
5390
+ const slotTypes: Record<string, SlotType> = {};
5391
+ const text = emitMethod(
5392
+ fn,
5393
+ mapType,
5394
+ resolver,
5395
+ constantTokens,
5396
+ urlParameters,
5397
+ bindingEvidence,
5398
+ slotTypes,
5399
+ );
5400
+ return { fn, text, slotTypes };
5401
+ }),
5402
+ }))
5403
+ .sort((a, b) => a.name.localeCompare(b.name));
5404
+ }
5405
+
5406
+ // An interface signed on one line, each member line followed by a space.
5407
+ function interfaceSignature(name: string, members: readonly string[]): string {
5408
+ return `interface ${name} { ${members.map((member) => `${member} `).join("")}}`;
5028
5409
  }
5029
5410
 
5030
5411
  /**
@@ -5184,24 +5565,29 @@ export function emitSymbolSignatures(
5184
5565
  });
5185
5566
  }
5186
5567
 
5568
+ // A handle interface signs as one line, as a struct does; each member is the
5569
+ // text its method's FUNCTION entry signs.
5187
5570
  const handleGroups = collectHandleMethodGroups(module);
5188
- for (const group of handleGroups.values()) {
5189
- for (const prepared of group) {
5190
- const slotTypes: Record<string, SlotType> = {};
5191
- out.push({
5192
- identity: fnIdentity(prepared.original),
5193
- tsSignature: emitMethod(
5194
- prepared,
5195
- mapType,
5196
- resolver,
5197
- constantTokens,
5198
- urlParameters,
5199
- bindingEvidence,
5200
- slotTypes,
5201
- ),
5202
- slotTypes,
5203
- });
5571
+ for (const { name, members } of collectHandleInterfaces(
5572
+ module.namespace,
5573
+ handleGroups,
5574
+ mapType,
5575
+ resolver,
5576
+ constantTokens,
5577
+ urlParameters,
5578
+ bindingEvidence,
5579
+ )) {
5580
+ for (const { fn, text, slotTypes } of members) {
5581
+ out.push({ identity: fnIdentity(fn.original), tsSignature: text, slotTypes });
5204
5582
  }
5583
+ out.push({
5584
+ identity: { namespace: module.namespace, kind: "TYPEDEF", name, signature: "" },
5585
+ tsSignature: interfaceSignature(
5586
+ name,
5587
+ members.map((member) => member.text),
5588
+ ),
5589
+ slotTypes: NO_SLOTS,
5590
+ });
5205
5591
  }
5206
5592
 
5207
5593
  const constants: PreparedConstant[] = [];
@@ -5230,7 +5616,7 @@ export function emitSymbolSignatures(
5230
5616
  // carries the first, which is verbatim in the `.d.ts` and is what the `/api`
5231
5617
  // render layer reduces to `<placeholder>: <type>`.
5232
5618
  for (const p of module.properties) {
5233
- const [firstMember = ""] = emitPropertyMembers(p, mapType, module.namespace);
5619
+ const [firstMember = ""] = emitPropertyMembers(p, mapType, module.namespace, bindingEvidence);
5234
5620
  out.push({
5235
5621
  identity: { namespace: module.namespace, kind: "PROPERTY", name: p.name, signature: "" },
5236
5622
  tsSignature: firstMember,
@@ -5240,13 +5626,14 @@ export function emitSymbolSignatures(
5240
5626
 
5241
5627
  // A struct signs as its interface on one line; each member line is verbatim in
5242
5628
  // the declarations.
5243
- for (const declaration of collectTypeDeclarations(
5629
+ const typeDeclarations = collectTypeDeclarations(
5244
5630
  module,
5245
- new Set(handleGroups.keys()),
5631
+ handleTypedefNames(module.namespace, handleGroups.keys()),
5246
5632
  rawMapType,
5247
5633
  mapType,
5248
5634
  bindingEvidence,
5249
- )) {
5635
+ );
5636
+ for (const declaration of typeDeclarations) {
5250
5637
  out.push({
5251
5638
  identity: {
5252
5639
  namespace: module.namespace,
@@ -5259,23 +5646,50 @@ export function emitSymbolSignatures(
5259
5646
  });
5260
5647
  }
5261
5648
 
5649
+ // An overload-slot alias is declared exactly where the declarations carry it,
5650
+ // so it signs from the same functions and the same declared set.
5651
+ for (const { name, declaration } of overloadSlotAliasDeclarations(
5652
+ [...module.functions, ...(options?.withheldFunctions ?? [])],
5653
+ typeDeclarations,
5654
+ prefix,
5655
+ mapType,
5656
+ constantTokens,
5657
+ urlParameters,
5658
+ bindingEvidence,
5659
+ )) {
5660
+ out.push({
5661
+ identity: { namespace: module.namespace, kind: "TYPEDEF", name, signature: "" },
5662
+ tsSignature: declaration,
5663
+ slotTypes: NO_SLOTS,
5664
+ });
5665
+ }
5666
+
5262
5667
  // The module's constant-union aliases travel as typedefs too: a signature that
5263
5668
  // names one carries no member list of its own, so the docs read the members
5264
5669
  // from here rather than from the emitter's table.
5265
- for (const row of CONSTANT_UNION_ALIASES.filter((r) => r.home === module.namespace)) {
5670
+ const enumAliases = collectEnumAliases(module, constants, nestedRoot);
5671
+ for (const row of ownedConstantUnionAliases(
5672
+ module.namespace,
5673
+ new Set(enumAliases.all.map((alias) => alias.path)),
5674
+ )) {
5675
+ const name = engineTypeName(row.name);
5266
5676
  out.push({
5267
- identity: { namespace: module.namespace, kind: "TYPEDEF", name: row.name, signature: "" },
5268
- tsSignature: unionAliasDeclaration(row.name, row.members),
5677
+ identity: { namespace: module.namespace, kind: "TYPEDEF", name, signature: "" },
5678
+ tsSignature: unionAliasDeclaration(name, row.members),
5269
5679
  slotTypes: NO_SLOTS,
5270
5680
  });
5271
5681
  }
5272
5682
 
5273
- // An enum alias is keyed by its local path (`STATUS`, `ui.COLOR`), as the
5274
- // constant-union aliases above are keyed by their local name.
5275
- const enumAliases = collectEnumAliases(module, constants, nestedRoot);
5683
+ // An enum alias is keyed by its declared local path (`Status`, `ui.Color`),
5684
+ // as the constant-union aliases above are keyed by their declared name.
5276
5685
  for (const alias of enumAliases.all) {
5277
5686
  out.push({
5278
- identity: { namespace: module.namespace, kind: "TYPEDEF", name: alias.path, signature: "" },
5687
+ identity: {
5688
+ namespace: module.namespace,
5689
+ kind: "TYPEDEF",
5690
+ name: renamedLeaf(alias.path),
5691
+ signature: "",
5692
+ },
5279
5693
  tsSignature: unionAliasDeclaration(alias.name, alias.members, alias.nilable),
5280
5694
  slotTypes: NO_SLOTS,
5281
5695
  });
@@ -5317,8 +5731,9 @@ interface NestedGroup {
5317
5731
  children: Map<string, NestedGroup>;
5318
5732
  }
5319
5733
 
5320
- // One `ENUM`'s union alias: `name` is the declared identifier, `path` the local
5321
- // path under the module namespace (`ui.COLOR`), `members` the member FQNs.
5734
+ // One `ENUM`'s union alias: `name` is the declared identifier (`Color`), `path`
5735
+ // the reference's local path under the module namespace (`ui.COLOR`), `members`
5736
+ // the member FQNs.
5322
5737
  interface EnumAlias {
5323
5738
  name: string;
5324
5739
  path: string;
@@ -5447,12 +5862,14 @@ function collectEnumAliases(
5447
5862
  const nilable = new Set(module.constants.filter((c) => c.nilable).map((c) => c.name));
5448
5863
  const topLevel: EnumAlias[] = [];
5449
5864
  const all: EnumAlias[] = [];
5450
- const enums = [...(module.enums ?? [])].sort((a, b) => a.name.localeCompare(b.name));
5865
+ const enums = [...(module.enums ?? [])].sort((a, b) =>
5866
+ renamedLeaf(a.name).localeCompare(renamedLeaf(b.name)),
5867
+ );
5451
5868
  for (const e of enums) {
5452
5869
  if (e.members.length === 0 || !e.members.every((m) => declared.has(m))) continue;
5453
5870
  const path = stripPrefix(e.name, prefix);
5454
5871
  if (TS_IDENTIFIER.test(path)) {
5455
- const alias = { name: path, path, members: e.members, nilable };
5872
+ const alias = { name: engineTypeName(path), path, members: e.members, nilable };
5456
5873
  topLevel.push(alias);
5457
5874
  all.push(alias);
5458
5875
  continue;
@@ -5468,7 +5885,7 @@ function collectEnumAliases(
5468
5885
  level = group.children;
5469
5886
  }
5470
5887
  if (group === undefined) continue;
5471
- const alias = { name, path, members: e.members, nilable };
5888
+ const alias = { name: engineTypeName(name), path, members: e.members, nilable };
5472
5889
  group.enumAliases.push(alias);
5473
5890
  all.push(alias);
5474
5891
  }
@@ -5749,6 +6166,7 @@ function memberSignature(
5749
6166
  constantTokens,
5750
6167
  elementName,
5751
6168
  urlParameters,
6169
+ bindingEvidence,
5752
6170
  required.has(i),
5753
6171
  prepared.retype?.get(i),
5754
6172
  ),
@@ -5764,6 +6182,7 @@ function memberSignature(
5764
6182
  constantTokens,
5765
6183
  elementName,
5766
6184
  urlParameters,
6185
+ bindingEvidence,
5767
6186
  );
5768
6187
  const params = [...positional.map((p) => p.text), ...(rest ? [rest.text] : [])].join(", ");
5769
6188
  const ret = emitReturn(
@@ -6029,6 +6448,7 @@ function parameterType(
6029
6448
  constantTokens: ConstantSlotTokens,
6030
6449
  elementName: string,
6031
6450
  urlParameters: UrlParameterTable,
6451
+ bindingEvidence: ReadonlySet<string>,
6032
6452
  ): string {
6033
6453
  const concrete = p.types.filter((t) => t !== "nil");
6034
6454
  // The caller supplies a parameter, so a string-keyed table in it also takes an
@@ -6050,10 +6470,19 @@ function parameterType(
6050
6470
  elementName,
6051
6471
  "param",
6052
6472
  p.name,
6473
+ bindingEvidence,
6053
6474
  )
6054
6475
  : "unknown";
6055
- const correction = PARAM_TYPE_CORRECTIONS.get(tableSlotKey(elementName, "param", p.name));
6056
- if (correction === undefined || !pinMatches(p.types, correctionPins(correction))) return mapped;
6476
+ const key = tableSlotKey(elementName, "param", p.name);
6477
+ const correction = PARAM_TYPE_CORRECTIONS.get(key);
6478
+ if (
6479
+ correction === undefined ||
6480
+ !pinMatches(p.types, correctionPins(correction)) ||
6481
+ (correction.binding !== undefined && !bindingEvidence.has(key))
6482
+ ) {
6483
+ return mapped;
6484
+ }
6485
+ if (correction.ts !== undefined) return correction.ts;
6057
6486
  const kept =
6058
6487
  correction.removes === undefined ? mapped : withoutUnionMembers(mapped, correction.removes);
6059
6488
  if (correction.adds === undefined) return kept;
@@ -6135,13 +6564,22 @@ function emitRestParameter(
6135
6564
  constantTokens: ConstantSlotTokens,
6136
6565
  elementName: string,
6137
6566
  urlParameters: UrlParameterTable,
6567
+ bindingEvidence: ReadonlySet<string>,
6138
6568
  ): { text: string; slots: EmittedSlot[] } {
6139
6569
  const vararg = params[varargIndex];
6140
6570
  if (vararg === undefined) return { text: "", slots: [] };
6141
6571
  const folded = params.slice(varargIndex).map((p, offset) => ({
6142
6572
  position: varargIndex + offset,
6143
6573
  name: p.name,
6144
- ts: parameterType(p, mapType, resolver, constantTokens, elementName, urlParameters),
6574
+ ts: parameterType(
6575
+ p,
6576
+ mapType,
6577
+ resolver,
6578
+ constantTokens,
6579
+ elementName,
6580
+ urlParameters,
6581
+ bindingEvidence,
6582
+ ),
6145
6583
  }));
6146
6584
  const members = [...new Set(folded.map((slot) => slot.ts))];
6147
6585
  const first = members[0] ?? "unknown";
@@ -6169,12 +6607,21 @@ function emitParameter(
6169
6607
  constantTokens: ConstantSlotTokens,
6170
6608
  elementName: string,
6171
6609
  urlParameters: UrlParameterTable,
6610
+ bindingEvidence: ReadonlySet<string>,
6172
6611
  required = false,
6173
6612
  retype: (type: string) => string = (type) => type,
6174
6613
  ): { text: string; type: string; optional: boolean } {
6175
6614
  const name = safeParamName(p.name, index);
6176
6615
  const ts = retype(
6177
- parameterType(p, mapType, resolver, constantTokens, elementName, urlParameters),
6616
+ parameterType(
6617
+ p,
6618
+ mapType,
6619
+ resolver,
6620
+ constantTokens,
6621
+ elementName,
6622
+ urlParameters,
6623
+ bindingEvidence,
6624
+ ),
6178
6625
  );
6179
6626
  // An interior doc-optional param (a required param follows, so the trailing-`?`
6180
6627
  // projection cannot mark it) keeps its optionality as `| undefined` — TSTL
@@ -6246,6 +6693,7 @@ function emitReturn(
6246
6693
  elementName,
6247
6694
  "return",
6248
6695
  rv.name,
6696
+ bindingEvidence,
6249
6697
  )
6250
6698
  : "unknown"),
6251
6699
  };
@@ -6284,6 +6732,7 @@ function emitReturn(
6284
6732
  elementName,
6285
6733
  "return",
6286
6734
  first.name,
6735
+ bindingEvidence,
6287
6736
  )
6288
6737
  : "unknown";
6289
6738
  // A LuaLS vararg return (`...`) is every value the call returns, however many.
@@ -6323,12 +6772,17 @@ function emitPropertyMembers(
6323
6772
  p: ApiProperty,
6324
6773
  mapType: DefoldTypeMapper,
6325
6774
  namespace: string,
6775
+ bindingEvidence: ReadonlySet<string>,
6326
6776
  ): readonly string[] {
6327
- const keyRange = PROPERTY_KEY_RANGES.get(`${namespace}.${p.name}`);
6777
+ const rangeKey = `${namespace}.${p.name}`;
6778
+ const keyRange = PROPERTY_KEY_RANGES.get(rangeKey);
6328
6779
  const keys: string[] = [];
6329
6780
  if (keyRange !== undefined) {
6330
- for (let unit = keyRange.first; unit <= keyRange.last; unit++)
6331
- keys.push(`${keyRange.prefix}${unit}`);
6781
+ const last =
6782
+ keyRange.engine !== undefined && bindingEvidence.has(rangeKey)
6783
+ ? keyRange.engine.last
6784
+ : keyRange.last;
6785
+ for (let unit = keyRange.first; unit <= last; unit++) keys.push(`${keyRange.prefix}${unit}`);
6332
6786
  } else {
6333
6787
  keys.push(TS_IDENTIFIER.test(p.name) ? p.name : JSON.stringify(p.name));
6334
6788
  }
@@ -6407,8 +6861,9 @@ function mapSlotUnion(
6407
6861
  resolver: TableDocResolver,
6408
6862
  constantTokens: ConstantSlotTokens,
6409
6863
  elementName: string,
6410
- slotKind?: "param" | "return",
6411
- slotName?: string,
6864
+ slotKind: "param" | "return" | undefined,
6865
+ slotName: string | undefined,
6866
+ bindingEvidence: ReadonlySet<string>,
6412
6867
  ): string {
6413
6868
  const mapped: string[] = [];
6414
6869
  const seen = new Set<string>();
@@ -6501,6 +6956,7 @@ function mapSlotUnion(
6501
6956
  slotKind,
6502
6957
  slotName,
6503
6958
  parseTableFields(doc, resolver, slotName) ?? [],
6959
+ bindingEvidence,
6504
6960
  ),
6505
6961
  );
6506
6962
  const object = inlineTableType(parsed, mapType, optionalFields);
@@ -6510,7 +6966,13 @@ function mapSlotUnion(
6510
6966
  if (parsed !== null) {
6511
6967
  const added = applyFieldAdditions(elementName, slotKind, slotName, parsed);
6512
6968
  const nested = applyNestedFieldCurations(elementName, slotKind, slotName, added);
6513
- const typed = applyFieldTypeOverrides(elementName, slotKind, slotName, nested);
6969
+ const typed = applyFieldTypeOverrides(
6970
+ elementName,
6971
+ slotKind,
6972
+ slotName,
6973
+ nested,
6974
+ bindingEvidence,
6975
+ );
6514
6976
  const required = applyRequiredFieldCorrections(
6515
6977
  elementName,
6516
6978
  slotKind,
@@ -6680,8 +7142,8 @@ function withoutSubsumedOpaqueArms(mapped: readonly string[]): string[] {
6680
7142
  });
6681
7143
  }
6682
7144
 
6683
- // Joins already-mapped arms, collapsing the branded ones to their alias when
6684
- // they are exactly a `CONSTANT_UNION_ALIASES` member set. The alias takes the
7145
+ // Joins already-mapped arms, collapsing the branded ones to their row's spelling
7146
+ // when they are exactly a `CONSTANT_UNION_ALIASES` member set. The spelling takes the
6685
7147
  // position of the first branded arm, so a mixed union keeps its reading order
6686
7148
  // (`gui.Easing | Vector`). Arms that are not constants are untouched, and a set
6687
7149
  // with no row keeps every arm inline.
@@ -6699,7 +7161,7 @@ function joinUnionArms(mapped: readonly string[]): string {
6699
7161
  for (const arm of arms) {
6700
7162
  if (BRAND_ARM.test(arm)) {
6701
7163
  if (placed) continue;
6702
- out.push(`${alias.home}.${alias.name}`);
7164
+ out.push(constantUnionSpelling(alias));
6703
7165
  placed = true;
6704
7166
  continue;
6705
7167
  }