@rezless/vm 0.6.2 → 0.8.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 (273) hide show
  1. package/dist/builtins/agents.d.ts +14 -0
  2. package/dist/builtins/agents.d.ts.map +1 -0
  3. package/dist/builtins/agents.js +117 -0
  4. package/dist/builtins/agents.js.map +1 -0
  5. package/dist/builtins/animation.d.ts +46 -0
  6. package/dist/builtins/animation.d.ts.map +1 -0
  7. package/dist/builtins/animation.js +316 -0
  8. package/dist/builtins/animation.js.map +1 -0
  9. package/dist/builtins/attachments.d.ts +9 -0
  10. package/dist/builtins/attachments.d.ts.map +1 -0
  11. package/dist/builtins/attachments.js +127 -0
  12. package/dist/builtins/attachments.js.map +1 -0
  13. package/dist/builtins/base64.d.ts +13 -2
  14. package/dist/builtins/base64.d.ts.map +1 -1
  15. package/dist/builtins/base64.js +8 -6
  16. package/dist/builtins/base64.js.map +1 -1
  17. package/dist/builtins/camera.d.ts +35 -0
  18. package/dist/builtins/camera.d.ts.map +1 -0
  19. package/dist/builtins/camera.js +104 -0
  20. package/dist/builtins/camera.js.map +1 -0
  21. package/dist/builtins/collision.d.ts +21 -0
  22. package/dist/builtins/collision.d.ts.map +1 -0
  23. package/dist/builtins/collision.js +44 -0
  24. package/dist/builtins/collision.js.map +1 -0
  25. package/dist/builtins/controls.d.ts +19 -0
  26. package/dist/builtins/controls.d.ts.map +1 -0
  27. package/dist/builtins/controls.js +27 -0
  28. package/dist/builtins/controls.js.map +1 -0
  29. package/dist/builtins/damage.d.ts +24 -0
  30. package/dist/builtins/damage.d.ts.map +1 -0
  31. package/dist/builtins/damage.js +166 -0
  32. package/dist/builtins/damage.js.map +1 -0
  33. package/dist/builtins/detected.d.ts +22 -1
  34. package/dist/builtins/detected.d.ts.map +1 -1
  35. package/dist/builtins/detected.js +28 -2
  36. package/dist/builtins/detected.js.map +1 -1
  37. package/dist/builtins/dialog.d.ts +23 -0
  38. package/dist/builtins/dialog.d.ts.map +1 -0
  39. package/dist/builtins/dialog.js +69 -0
  40. package/dist/builtins/dialog.js.map +1 -0
  41. package/dist/builtins/email.d.ts +61 -0
  42. package/dist/builtins/email.d.ts.map +1 -0
  43. package/dist/builtins/email.js +121 -0
  44. package/dist/builtins/email.js.map +1 -0
  45. package/dist/builtins/environment.d.ts +27 -0
  46. package/dist/builtins/environment.d.ts.map +1 -0
  47. package/dist/builtins/environment.js +126 -0
  48. package/dist/builtins/environment.js.map +1 -0
  49. package/dist/builtins/experience.d.ts +51 -0
  50. package/dist/builtins/experience.d.ts.map +1 -0
  51. package/dist/builtins/experience.js +202 -0
  52. package/dist/builtins/experience.js.map +1 -0
  53. package/dist/builtins/game-control.d.ts +17 -0
  54. package/dist/builtins/game-control.d.ts.map +1 -0
  55. package/dist/builtins/game-control.js +21 -0
  56. package/dist/builtins/game-control.js.map +1 -0
  57. package/dist/builtins/give.d.ts +16 -0
  58. package/dist/builtins/give.d.ts.map +1 -0
  59. package/dist/builtins/give.js +137 -0
  60. package/dist/builtins/give.js.map +1 -0
  61. package/dist/builtins/ground.d.ts +18 -0
  62. package/dist/builtins/ground.d.ts.map +1 -0
  63. package/dist/builtins/ground.js +83 -0
  64. package/dist/builtins/ground.js.map +1 -0
  65. package/dist/builtins/hash.d.ts +1 -0
  66. package/dist/builtins/hash.d.ts.map +1 -1
  67. package/dist/builtins/hash.js +12 -2
  68. package/dist/builtins/hash.js.map +1 -1
  69. package/dist/builtins/http.d.ts +99 -4
  70. package/dist/builtins/http.d.ts.map +1 -1
  71. package/dist/builtins/http.js +216 -6
  72. package/dist/builtins/http.js.map +1 -1
  73. package/dist/builtins/index.d.ts.map +1 -1
  74. package/dist/builtins/index.js +250 -14
  75. package/dist/builtins/index.js.map +1 -1
  76. package/dist/builtins/inventory.d.ts +8 -5
  77. package/dist/builtins/inventory.d.ts.map +1 -1
  78. package/dist/builtins/inventory.js +37 -15
  79. package/dist/builtins/inventory.js.map +1 -1
  80. package/dist/builtins/json.d.ts.map +1 -1
  81. package/dist/builtins/json.js +45 -34
  82. package/dist/builtins/json.js.map +1 -1
  83. package/dist/builtins/land.d.ts +29 -0
  84. package/dist/builtins/land.d.ts.map +1 -0
  85. package/dist/builtins/land.js +339 -0
  86. package/dist/builtins/land.js.map +1 -0
  87. package/dist/builtins/link-info.js +2 -2
  88. package/dist/builtins/link-info.js.map +1 -1
  89. package/dist/builtins/links.d.ts +20 -0
  90. package/dist/builtins/links.d.ts.map +1 -0
  91. package/dist/builtins/links.js +53 -0
  92. package/dist/builtins/links.js.map +1 -0
  93. package/dist/builtins/linksetdata.d.ts +5 -0
  94. package/dist/builtins/linksetdata.d.ts.map +1 -1
  95. package/dist/builtins/linksetdata.js +22 -9
  96. package/dist/builtins/linksetdata.js.map +1 -1
  97. package/dist/builtins/lists.d.ts +21 -0
  98. package/dist/builtins/lists.d.ts.map +1 -1
  99. package/dist/builtins/lists.js +137 -17
  100. package/dist/builtins/lists.js.map +1 -1
  101. package/dist/builtins/math.d.ts +18 -0
  102. package/dist/builtins/math.d.ts.map +1 -1
  103. package/dist/builtins/math.js +104 -5
  104. package/dist/builtins/math.js.map +1 -1
  105. package/dist/builtins/media.d.ts +8 -0
  106. package/dist/builtins/media.d.ts.map +1 -0
  107. package/dist/builtins/media.js +167 -0
  108. package/dist/builtins/media.js.map +1 -0
  109. package/dist/builtins/motion.d.ts +79 -0
  110. package/dist/builtins/motion.d.ts.map +1 -0
  111. package/dist/builtins/motion.js +396 -0
  112. package/dist/builtins/motion.js.map +1 -0
  113. package/dist/builtins/notecard-search.d.ts +15 -0
  114. package/dist/builtins/notecard-search.d.ts.map +1 -0
  115. package/dist/builtins/notecard-search.js +374 -0
  116. package/dist/builtins/notecard-search.js.map +1 -0
  117. package/dist/builtins/object-animation.d.ts +21 -0
  118. package/dist/builtins/object-animation.d.ts.map +1 -0
  119. package/dist/builtins/object-animation.js +193 -0
  120. package/dist/builtins/object-animation.js.map +1 -0
  121. package/dist/builtins/object-reads.d.ts +39 -0
  122. package/dist/builtins/object-reads.d.ts.map +1 -0
  123. package/dist/builtins/object-reads.js +332 -0
  124. package/dist/builtins/object-reads.js.map +1 -0
  125. package/dist/builtins/object.d.ts +11 -2
  126. package/dist/builtins/object.d.ts.map +1 -1
  127. package/dist/builtins/object.js +32 -8
  128. package/dist/builtins/object.js.map +1 -1
  129. package/dist/builtins/particles.d.ts +4 -0
  130. package/dist/builtins/particles.d.ts.map +1 -0
  131. package/dist/builtins/particles.js +65 -0
  132. package/dist/builtins/particles.js.map +1 -0
  133. package/dist/builtins/pathfinding.d.ts +39 -0
  134. package/dist/builtins/pathfinding.d.ts.map +1 -0
  135. package/dist/builtins/pathfinding.js +83 -0
  136. package/dist/builtins/pathfinding.js.map +1 -0
  137. package/dist/builtins/permissions.d.ts.map +1 -1
  138. package/dist/builtins/permissions.js +6 -0
  139. package/dist/builtins/permissions.js.map +1 -1
  140. package/dist/builtins/physics.d.ts +49 -0
  141. package/dist/builtins/physics.d.ts.map +1 -0
  142. package/dist/builtins/physics.js +47 -0
  143. package/dist/builtins/physics.js.map +1 -0
  144. package/dist/builtins/prim-accessors.d.ts +4 -0
  145. package/dist/builtins/prim-accessors.d.ts.map +1 -1
  146. package/dist/builtins/prim-accessors.js +75 -28
  147. package/dist/builtins/prim-accessors.js.map +1 -1
  148. package/dist/builtins/prim-params.d.ts.map +1 -1
  149. package/dist/builtins/prim-params.js +6 -2
  150. package/dist/builtins/prim-params.js.map +1 -1
  151. package/dist/builtins/raycast.d.ts +3 -0
  152. package/dist/builtins/raycast.d.ts.map +1 -0
  153. package/dist/builtins/raycast.js +65 -0
  154. package/dist/builtins/raycast.js.map +1 -0
  155. package/dist/builtins/region.d.ts +31 -0
  156. package/dist/builtins/region.d.ts.map +1 -0
  157. package/dist/builtins/region.js +125 -0
  158. package/dist/builtins/region.js.map +1 -0
  159. package/dist/builtins/remote-load.d.ts +3 -0
  160. package/dist/builtins/remote-load.d.ts.map +1 -0
  161. package/dist/builtins/remote-load.js +53 -0
  162. package/dist/builtins/remote-load.js.map +1 -0
  163. package/dist/builtins/rez.d.ts +10 -0
  164. package/dist/builtins/rez.d.ts.map +1 -0
  165. package/dist/builtins/rez.js +255 -0
  166. package/dist/builtins/rez.js.map +1 -0
  167. package/dist/builtins/rsa.d.ts +4 -0
  168. package/dist/builtins/rsa.d.ts.map +1 -0
  169. package/dist/builtins/rsa.js +44 -0
  170. package/dist/builtins/rsa.js.map +1 -0
  171. package/dist/builtins/script-control.d.ts +8 -3
  172. package/dist/builtins/script-control.d.ts.map +1 -1
  173. package/dist/builtins/script-control.js +27 -8
  174. package/dist/builtins/script-control.js.map +1 -1
  175. package/dist/builtins/sensor.d.ts +11 -0
  176. package/dist/builtins/sensor.d.ts.map +1 -0
  177. package/dist/builtins/sensor.js +145 -0
  178. package/dist/builtins/sensor.js.map +1 -0
  179. package/dist/builtins/sit.d.ts +23 -0
  180. package/dist/builtins/sit.d.ts.map +1 -0
  181. package/dist/builtins/sit.js +150 -0
  182. package/dist/builtins/sit.js.map +1 -0
  183. package/dist/builtins/sound.d.ts +19 -0
  184. package/dist/builtins/sound.d.ts.map +1 -0
  185. package/dist/builtins/sound.js +113 -0
  186. package/dist/builtins/sound.js.map +1 -0
  187. package/dist/builtins/strings.d.ts +6 -0
  188. package/dist/builtins/strings.d.ts.map +1 -1
  189. package/dist/builtins/strings.js +47 -6
  190. package/dist/builtins/strings.js.map +1 -1
  191. package/dist/builtins/targets.d.ts +12 -0
  192. package/dist/builtins/targets.d.ts.map +1 -0
  193. package/dist/builtins/targets.js +121 -0
  194. package/dist/builtins/targets.js.map +1 -0
  195. package/dist/builtins/time.d.ts +15 -5
  196. package/dist/builtins/time.d.ts.map +1 -1
  197. package/dist/builtins/time.js +36 -11
  198. package/dist/builtins/time.js.map +1 -1
  199. package/dist/builtins/vehicles.d.ts +22 -0
  200. package/dist/builtins/vehicles.d.ts.map +1 -0
  201. package/dist/builtins/vehicles.js +78 -0
  202. package/dist/builtins/vehicles.js.map +1 -0
  203. package/dist/builtins/viewer.d.ts +39 -0
  204. package/dist/builtins/viewer.d.ts.map +1 -0
  205. package/dist/builtins/viewer.js +74 -0
  206. package/dist/builtins/viewer.js.map +1 -0
  207. package/dist/clock.d.ts +45 -7
  208. package/dist/clock.d.ts.map +1 -1
  209. package/dist/clock.js +125 -14
  210. package/dist/clock.js.map +1 -1
  211. package/dist/dispatch.d.ts +3 -2
  212. package/dist/dispatch.d.ts.map +1 -1
  213. package/dist/dispatch.js +15 -10
  214. package/dist/dispatch.js.map +1 -1
  215. package/dist/experience.d.ts +39 -0
  216. package/dist/experience.d.ts.map +1 -0
  217. package/dist/experience.js +38 -0
  218. package/dist/experience.js.map +1 -0
  219. package/dist/index.d.ts +20 -3
  220. package/dist/index.d.ts.map +1 -1
  221. package/dist/index.js +1 -0
  222. package/dist/index.js.map +1 -1
  223. package/dist/interpreter.d.ts +5 -10
  224. package/dist/interpreter.d.ts.map +1 -1
  225. package/dist/interpreter.js +143 -70
  226. package/dist/interpreter.js.map +1 -1
  227. package/dist/inventory.d.ts +2 -0
  228. package/dist/inventory.d.ts.map +1 -1
  229. package/dist/inventory.js +2 -0
  230. package/dist/inventory.js.map +1 -1
  231. package/dist/linkset.d.ts +147 -11
  232. package/dist/linkset.d.ts.map +1 -1
  233. package/dist/linkset.js +401 -28
  234. package/dist/linkset.js.map +1 -1
  235. package/dist/memory.d.ts +5 -2
  236. package/dist/memory.d.ts.map +1 -1
  237. package/dist/memory.js +5 -2
  238. package/dist/memory.js.map +1 -1
  239. package/dist/prim-params.d.ts +27 -10
  240. package/dist/prim-params.d.ts.map +1 -1
  241. package/dist/prim-params.js +209 -125
  242. package/dist/prim-params.js.map +1 -1
  243. package/dist/prim.d.ts +95 -5
  244. package/dist/prim.d.ts.map +1 -1
  245. package/dist/prim.js +96 -7
  246. package/dist/prim.js.map +1 -1
  247. package/dist/runtime.d.ts +52 -3
  248. package/dist/runtime.d.ts.map +1 -1
  249. package/dist/script.d.ts +171 -12
  250. package/dist/script.d.ts.map +1 -1
  251. package/dist/script.js +480 -73
  252. package/dist/script.js.map +1 -1
  253. package/dist/typecheck.d.ts +3 -1
  254. package/dist/typecheck.d.ts.map +1 -1
  255. package/dist/typecheck.js +25 -5
  256. package/dist/typecheck.js.map +1 -1
  257. package/dist/values/coerce.d.ts +1 -1
  258. package/dist/values/coerce.d.ts.map +1 -1
  259. package/dist/values/coerce.js +10 -6
  260. package/dist/values/coerce.js.map +1 -1
  261. package/dist/values/format.d.ts +3 -1
  262. package/dist/values/format.d.ts.map +1 -1
  263. package/dist/values/format.js +10 -6
  264. package/dist/values/format.js.map +1 -1
  265. package/dist/values/ops.d.ts +3 -0
  266. package/dist/values/ops.d.ts.map +1 -1
  267. package/dist/values/ops.js +2 -2
  268. package/dist/values/ops.js.map +1 -1
  269. package/dist/world.d.ts +247 -1
  270. package/dist/world.d.ts.map +1 -1
  271. package/dist/world.js +318 -6
  272. package/dist/world.js.map +1 -1
  273. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
- import { ALL_SIDES, CLICK_ACTION_TOUCH, PRIM_ALPHA_MODE, PRIM_ALPHA_MODE_NONE, PRIM_ALLOW_UNSIT, PRIM_BUMP_NONE, PRIM_BUMP_SHINY, PRIM_CAST_SHADOWS, PRIM_CLICK_ACTION, PRIM_COLOR, PRIM_DAMAGE, PRIM_DESC, PRIM_FLEXIBLE, PRIM_FULLBRIGHT, PRIM_GLOW, PRIM_GLTF_ALPHA_MODE_OPAQUE, PRIM_GLTF_BASE_COLOR, PRIM_GLTF_EMISSIVE, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_NORMAL, PRIM_HEALTH, PRIM_HOLE_DEFAULT, PRIM_LINK_TARGET, PRIM_MATERIAL, PRIM_MATERIAL_WOOD, PRIM_NAME, PRIM_NORMAL, PRIM_OMEGA, PRIM_PHANTOM, PRIM_PHYSICS, PRIM_PHYSICS_SHAPE_PRIM, PRIM_PHYSICS_SHAPE_TYPE, PRIM_POINT_LIGHT, PRIM_POSITION, PRIM_POS_LOCAL, PRIM_PROJECTOR, PRIM_REFLECTION_PROBE, PRIM_RENDER_MATERIAL, PRIM_ROTATION, PRIM_ROT_LOCAL, PRIM_SCRIPTED_SIT_ONLY, PRIM_SCULPT_TYPE_SPHERE, PRIM_SHINY_NONE, PRIM_SIT_FLAGS, PRIM_SIT_TARGET, PRIM_SIZE, PRIM_SLICE, PRIM_SPECULAR, PRIM_TEMP_ON_REZ, PRIM_TEXGEN, PRIM_TEXGEN_DEFAULT, PRIM_TEXT, PRIM_TEXTURE, PRIM_TYPE, PRIM_TYPE_BOX, PRIM_TYPE_CYLINDER, PRIM_TYPE_PRISM, PRIM_TYPE_RING, PRIM_TYPE_SCULPT, PRIM_TYPE_SPHERE, PRIM_TYPE_TORUS, PRIM_TYPE_TUBE, STATUS_PHANTOM, STATUS_PHYSICS, TEXTURE_DEFAULT, } from './generated/constants.js';
1
+ import { CLICK_ACTION_TOUCH, OVERRIDE_GLTF_BASE_ALPHA, OVERRIDE_GLTF_BASE_ALPHA_MASK, OVERRIDE_GLTF_BASE_ALPHA_MODE, OVERRIDE_GLTF_BASE_COLOR_FACTOR, OVERRIDE_GLTF_BASE_DOUBLE_SIDED, OVERRIDE_GLTF_EMISSIVE_FACTOR, OVERRIDE_GLTF_METALLIC_FACTOR, OVERRIDE_GLTF_ROUGHNESS_FACTOR, PRIM_ALPHA_MODE, PRIM_ALPHA_MODE_BLEND, PRIM_ALPHA_MODE_MASK, PRIM_ALLOW_UNSIT, PRIM_BUMP_NONE, PRIM_BUMP_SHINY, PRIM_CAST_SHADOWS, PRIM_CLICK_ACTION, PRIM_COLOR, PRIM_DAMAGE, PRIM_DESC, PRIM_FLEXIBLE, PRIM_FULLBRIGHT, PRIM_GLOW, PRIM_GLTF_BASE_COLOR, PRIM_GLTF_EMISSIVE, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_NORMAL, PRIM_HEALTH, PRIM_HOLE_DEFAULT, PRIM_LINK_TARGET, PRIM_MATERIAL, PRIM_MATERIAL_WOOD, PRIM_NAME, PRIM_NORMAL, PRIM_OMEGA, PRIM_PHANTOM, PRIM_PHYSICS, PRIM_PHYSICS_SHAPE_PRIM, PRIM_PHYSICS_SHAPE_TYPE, PRIM_POINT_LIGHT, PRIM_POSITION, PRIM_POS_LOCAL, PRIM_PROJECTOR, PRIM_REFLECTION_PROBE, PRIM_RENDER_MATERIAL, PRIM_ROTATION, PRIM_ROT_LOCAL, PRIM_SCRIPTED_SIT_ONLY, PRIM_SCULPT_TYPE_SPHERE, PRIM_SHINY_NONE, PRIM_SIT_FLAGS, PRIM_SIT_TARGET, PRIM_SIZE, PRIM_SLICE, PRIM_SPECULAR, PRIM_TEMP_ON_REZ, PRIM_TEXGEN, PRIM_TEXGEN_DEFAULT, PRIM_TEXT, PRIM_TEXTURE, PRIM_TYPE, PRIM_TYPE_BOX, PRIM_TYPE_CYLINDER, PRIM_TYPE_PRISM, PRIM_TYPE_RING, PRIM_TYPE_SCULPT, PRIM_TYPE_SPHERE, PRIM_TYPE_TORUS, PRIM_TYPE_TUBE, STATUS_PHANTOM, STATUS_PHYSICS, TEXTURE_DEFAULT, } from './generated/constants.js';
2
2
  import { NULL_KEY, isRotation, isVector } from './values/types.js';
3
3
  import { listElementType } from './values/list-types.js';
4
+ import { InventoryType } from './inventory.js';
4
5
  import { truncateUtf8 } from './values/utf8.js';
5
6
  /**
6
7
  * Typed-literal aliases for the PRIM_TYPE_* constants. The
@@ -45,18 +46,109 @@ export function defaultFace() {
45
46
  fullBright: false,
46
47
  glow: 0,
47
48
  texgen: PRIM_TEXGEN_DEFAULT,
48
- renderMaterial: '',
49
- normal: defaultFaceMaterial(),
50
- specular: { ...defaultFaceMaterial(), color: one(), glossiness: 51, environment: 0 },
51
- alphaMode: { mode: PRIM_ALPHA_MODE_NONE, cutoff: 0 },
49
+ renderMaterial: NULL_KEY,
50
+ normal: { ...defaultFaceMaterial(), texture: NULL_KEY },
51
+ specular: { ...defaultFaceMaterial(), texture: NULL_KEY, color: one(), glossiness: 51, environment: 0 },
52
+ alphaMode: { mode: PRIM_ALPHA_MODE_BLEND, cutoff: 0 },
52
53
  gltf: {
53
- baseColor: { ...defaultFaceMaterial(), color: one(), alpha: 1, alphaMode: PRIM_GLTF_ALPHA_MODE_OPAQUE, alphaCutoff: 0.5, doubleSided: 0 },
54
+ baseColor: { ...defaultFaceMaterial(), color: '', alpha: '', alphaMode: '', alphaCutoff: '', doubleSided: '' },
54
55
  normal: defaultFaceMaterial(),
55
- metallicRoughness: { ...defaultFaceMaterial(), metallic: 1, roughness: 1 },
56
- emissive: { ...defaultFaceMaterial(), tint: zero() },
56
+ metallicRoughness: { ...defaultFaceMaterial(), metallic: '', roughness: '' },
57
+ emissive: { ...defaultFaceMaterial(), tint: '' },
57
58
  },
58
59
  };
59
60
  }
61
+ function clearGltfOverrides(g) {
62
+ g.baseColor = { ...g.baseColor, texture: '', color: '', alpha: '', alphaMode: '', alphaCutoff: '', doubleSided: '' };
63
+ g.normal = { ...g.normal, texture: '' };
64
+ g.metallicRoughness = { ...g.metallicRoughness, texture: '', metallic: '', roughness: '' };
65
+ g.emissive = { ...g.emissive, texture: '', tint: '' };
66
+ }
67
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
68
+ const findItem = (prim, type, name) => prim.inventory.find((it) => it.type === type && it.name === name);
69
+ /** The key a render material argument names: "" (none), a key, or a material item's name; null if it names nothing. */
70
+ export function renderMaterialKey(prim, material) {
71
+ if (material === '')
72
+ return NULL_KEY;
73
+ if (UUID.test(material))
74
+ return material;
75
+ return findItem(prim, InventoryType.MATERIAL, material)?.key ?? null;
76
+ }
77
+ export function textureKey(prim, texture) {
78
+ if (texture === '')
79
+ return NULL_KEY;
80
+ if (UUID.test(texture))
81
+ return texture;
82
+ return findItem(prim, InventoryType.TEXTURE, texture)?.key ?? null;
83
+ }
84
+ const itemName = (prim, type, key) => key === NULL_KEY ? key : (prim.inventory.find((it) => it.type === type && it.key === key)?.name ?? key);
85
+ export const textureName = (prim, key) => itemName(prim, InventoryType.TEXTURE, key);
86
+ export const materialName = (prim, key) => itemName(prim, InventoryType.MATERIAL, key);
87
+ const OVERRIDE_TYPES = {
88
+ [OVERRIDE_GLTF_BASE_COLOR_FACTOR]: 'vector',
89
+ [OVERRIDE_GLTF_BASE_ALPHA]: 'float',
90
+ [OVERRIDE_GLTF_BASE_ALPHA_MODE]: 'integer',
91
+ [OVERRIDE_GLTF_BASE_ALPHA_MASK]: 'float',
92
+ [OVERRIDE_GLTF_BASE_DOUBLE_SIDED]: 'integer',
93
+ [OVERRIDE_GLTF_METALLIC_FACTOR]: 'float',
94
+ [OVERRIDE_GLTF_ROUGHNESS_FACTOR]: 'float',
95
+ [OVERRIDE_GLTF_EMISSIVE_FACTOR]: 'vector',
96
+ };
97
+ const overrideType = (code) => (typeof code === 'number' ? OVERRIDE_TYPES[code] : undefined);
98
+ /**
99
+ * Apply llSetLinkGLTFOverrides' [OVERRIDE_GLTF_*, value, ...] pairs. The base
100
+ * colour and alpha form one factor that "" leaves alone; "" clears the others.
101
+ * One wrong-typed value rejects the whole call; an unknown code is skipped.
102
+ */
103
+ export function setGltfOverrides(g, params) {
104
+ for (let i = 0; i + 1 < params.length; i += 2) {
105
+ const expected = overrideType(params[i]);
106
+ if (expected === undefined || params[i + 1] === '')
107
+ continue;
108
+ const given = givenType(params, i + 1);
109
+ if (given !== expected && !(expected === 'float' && given === 'integer'))
110
+ return;
111
+ }
112
+ const bc = g.baseColor;
113
+ for (let i = 0; i + 1 < params.length; i += 2) {
114
+ const v = params[i + 1];
115
+ const n = typeof v === 'number' ? v : '';
116
+ switch (params[i]) {
117
+ case OVERRIDE_GLTF_BASE_COLOR_FACTOR:
118
+ if (isVector(v)) {
119
+ bc.color = clampVec(asVec(v), 0, 1);
120
+ if (bc.alpha === '')
121
+ bc.alpha = 1;
122
+ }
123
+ break;
124
+ case OVERRIDE_GLTF_BASE_ALPHA:
125
+ if (typeof v === 'number') {
126
+ bc.alpha = clamp(v, 0, 1);
127
+ if (bc.color === '')
128
+ bc.color = one();
129
+ }
130
+ break;
131
+ case OVERRIDE_GLTF_BASE_ALPHA_MODE:
132
+ bc.alphaMode = n === '' ? '' : clamp(n, 0, 2);
133
+ break;
134
+ case OVERRIDE_GLTF_BASE_ALPHA_MASK:
135
+ bc.alphaCutoff = n === '' ? '' : clamp(n, 0, 1);
136
+ break;
137
+ case OVERRIDE_GLTF_BASE_DOUBLE_SIDED:
138
+ bc.doubleSided = n === '' ? '' : clamp(n, 0, 1);
139
+ break;
140
+ case OVERRIDE_GLTF_METALLIC_FACTOR:
141
+ g.metallicRoughness.metallic = n === '' ? '' : clamp(n, 0, 1);
142
+ break;
143
+ case OVERRIDE_GLTF_ROUGHNESS_FACTOR:
144
+ g.metallicRoughness.roughness = n === '' ? '' : clamp(n, 0, 1);
145
+ break;
146
+ case OVERRIDE_GLTF_EMISSIVE_FACTOR:
147
+ g.emissive.tint = isVector(v) ? clampVec(asVec(v), 0, 1) : '';
148
+ break;
149
+ }
150
+ }
151
+ }
60
152
  export function defaultShape() {
61
153
  return {
62
154
  kind: SHAPE_BOX,
@@ -74,7 +166,7 @@ export function defaultPrimParams() {
74
166
  rotation: identityRot(),
75
167
  size: { x: 0.5, y: 0.5, z: 0.5 },
76
168
  faces: Array.from({ length: 6 }, () => defaultFace()),
77
- pointLight: { enabled: false, color: one(), intensity: 1, radius: 10, falloff: 0.75 },
169
+ pointLight: { enabled: false, color: zero(), intensity: 0, radius: 0, falloff: 0 },
78
170
  glow: [0, 0, 0, 0, 0, 0],
79
171
  omega: { axis: zero(), spinrate: 0, gain: 0 },
80
172
  physicsShapeType: PRIM_PHYSICS_SHAPE_PRIM,
@@ -92,7 +184,7 @@ export function defaultPrimParams() {
92
184
  projector: { texture: NULL_KEY, fov: 0, focus: 0, ambiance: 0 },
93
185
  clickAction: CLICK_ACTION_TOUCH,
94
186
  damage: { amount: 0, type: 0 },
95
- health: 1,
187
+ health: 0,
96
188
  reflectionProbe: { enabled: false, ambiance: 0, clipDistance: 0, flags: 0 },
97
189
  physicsMaterial: { gravityMultiplier: 1, restitution: 0.5, friction: Math.fround(0.6), density: 1000 },
98
190
  textureAnim: { mode: 0, face: 0, sizex: 0, sizey: 0, start: 0, length: 0, rate: 0 },
@@ -128,11 +220,11 @@ function normalizeRot(r) {
128
220
  return identityRot();
129
221
  return { x: r.x / mag, y: r.y / mag, z: r.z / mag, s: r.s / mag };
130
222
  }
131
- /** Face indexes a face argument names: every face for ALL_SIDES, none past the shape's face count. */
223
+ /** Face indexes a face argument names: every face for a negative face, none past the shape's face count. */
132
224
  export function faceIndices(p, face) {
133
- if (face === ALL_SIDES)
225
+ if (face < 0)
134
226
  return p.faces.map((_, i) => i);
135
- return face >= 0 && face < p.faces.length ? [face] : [];
227
+ return face < p.faces.length ? [face] : [];
136
228
  }
137
229
  const facesOf = (p, faceArg) => faceIndices(p, num(faceArg, 0) | 0);
138
230
  /**
@@ -214,20 +306,23 @@ export function writePrimParamSlots(param, rules, cursor) {
214
306
  * advances by `1 + consumed`). Returns `null` for unsupported / unknown
215
307
  * rule constants — the walker stops parsing the list, matching LSL.
216
308
  */
217
- export function writePrimParam(prim, param, rules, cursor) {
309
+ export function writePrimParam(prim, param, rules, cursor, caller = prim) {
218
310
  const p = prim.params;
219
311
  switch (param) {
220
312
  case PRIM_NAME:
221
- prim.name = truncateUtf8(str(rules[cursor]), 63);
313
+ prim.name = truncateUtf8(plainText(str(rules[cursor])), 63);
222
314
  return 1;
223
315
  case PRIM_DESC:
224
- prim.description = truncateUtf8(str(rules[cursor]), 127);
316
+ prim.description = truncateUtf8(plainText(str(rules[cursor])), 127);
225
317
  prim.appearance.description = prim.description;
226
318
  return 1;
227
319
  case PRIM_TEXT: {
228
320
  const text = truncateUtf8(str(rules[cursor]), 254);
229
321
  const color = asVec(rules[cursor + 1]);
230
322
  const alpha = num(rules[cursor + 2], 1);
323
+ const outOfRange = (v) => v < 0 || v > 1;
324
+ if (outOfRange(alpha) || outOfRange(color.x) || outOfRange(color.y) || outOfRange(color.z))
325
+ return 3;
231
326
  prim.appearance.text = { text, color, alpha };
232
327
  return 3;
233
328
  }
@@ -256,6 +351,7 @@ export function writePrimParam(prim, param, rules, cursor) {
256
351
  return 1;
257
352
  case PRIM_TEMP_ON_REZ:
258
353
  p.tempOnRez = num(rules[cursor]) !== 0;
354
+ prim.linkset?.setTemporary(p.tempOnRez);
259
355
  return 1;
260
356
  case PRIM_PHYSICS_SHAPE_TYPE:
261
357
  p.physicsShapeType = num(rules[cursor]) | 0;
@@ -281,9 +377,16 @@ export function writePrimParam(prim, param, rules, cursor) {
281
377
  case PRIM_DAMAGE:
282
378
  p.damage = { amount: num(rules[cursor]), type: num(rules[cursor + 1]) | 0 };
283
379
  return 2;
284
- case PRIM_OMEGA:
285
- p.omega = { axis: asVec(rules[cursor]), spinrate: num(rules[cursor + 1]), gain: num(rules[cursor + 2]) };
380
+ case PRIM_OMEGA: {
381
+ const axis = asVec(rules[cursor]);
382
+ const length = Math.hypot(axis.x, axis.y, axis.z) || 1;
383
+ p.omega = {
384
+ axis: { x: axis.x / length, y: axis.y / length, z: axis.z / length },
385
+ spinrate: num(rules[cursor + 1]) * length,
386
+ gain: num(rules[cursor + 2]),
387
+ };
286
388
  return 3;
389
+ }
287
390
  case PRIM_POINT_LIGHT:
288
391
  p.pointLight = {
289
392
  enabled: num(rules[cursor]) !== 0,
@@ -341,29 +444,33 @@ export function writePrimParam(prim, param, rules, cursor) {
341
444
  }
342
445
  case PRIM_TEXTURE: {
343
446
  const faces = facesOf(p, rules[cursor]);
344
- const tex = str(rules[cursor + 1]);
447
+ const tex = textureKey(caller, str(rules[cursor + 1])) ?? NULL_KEY;
345
448
  const repeats = asVec(rules[cursor + 2]);
346
449
  const offsets = asVec(rules[cursor + 3]);
347
450
  const rot = num(rules[cursor + 4]);
348
451
  for (const f of faces) {
349
452
  p.faces[f].texture = tex;
350
- p.faces[f].textureRepeats = { x: repeats.x, y: repeats.y, z: repeats.z };
351
- p.faces[f].textureOffsets = { x: offsets.x, y: offsets.y, z: offsets.z };
453
+ p.faces[f].textureRepeats = { x: repeats.x, y: repeats.y, z: 0 };
454
+ p.faces[f].textureOffsets = { x: offsets.x, y: offsets.y, z: 0 };
352
455
  p.faces[f].textureRotation = rot;
353
456
  }
354
457
  return 5;
355
458
  }
356
459
  case PRIM_RENDER_MATERIAL: {
357
460
  const faces = facesOf(p, rules[cursor]);
358
- const m = str(rules[cursor + 1]);
461
+ const m = renderMaterialKey(caller, str(rules[cursor + 1])) ?? NULL_KEY;
359
462
  for (const f of faces) {
360
463
  p.faces[f].renderMaterial = m;
464
+ clearGltfOverrides(p.faces[f].gltf);
361
465
  }
362
466
  return 2;
363
467
  }
364
468
  case PRIM_FULLBRIGHT: {
365
469
  const faces = facesOf(p, rules[cursor]);
366
- const v = num(rules[cursor + 1]) !== 0;
470
+ const raw = num(rules[cursor + 1]);
471
+ if (raw !== 0 && raw !== 1)
472
+ return 2;
473
+ const v = raw === 1;
367
474
  for (const f of faces) {
368
475
  p.faces[f].fullBright = v;
369
476
  }
@@ -382,6 +489,8 @@ export function writePrimParam(prim, param, rules, cursor) {
382
489
  const faces = facesOf(p, rules[cursor]);
383
490
  const shiny = num(rules[cursor + 1]) | 0;
384
491
  const bump = num(rules[cursor + 2]) | 0;
492
+ if (shiny < 0 || shiny > 3 || bump < 0 || bump > 17)
493
+ return 3;
385
494
  for (const f of faces) {
386
495
  p.faces[f].bumpShiny = { shiny, bump };
387
496
  }
@@ -390,6 +499,8 @@ export function writePrimParam(prim, param, rules, cursor) {
390
499
  case PRIM_TEXGEN: {
391
500
  const faces = facesOf(p, rules[cursor]);
392
501
  const mode = num(rules[cursor + 1]) | 0;
502
+ if (mode < 0 || mode > 1)
503
+ return 2;
393
504
  for (const f of faces) {
394
505
  p.faces[f].texgen = mode;
395
506
  }
@@ -398,12 +509,13 @@ export function writePrimParam(prim, param, rules, cursor) {
398
509
  case PRIM_NORMAL: {
399
510
  const faces = facesOf(p, rules[cursor]);
400
511
  const tex = str(rules[cursor + 1]);
401
- const repeats = asVec(rules[cursor + 2]);
402
- const offsets = asVec(rules[cursor + 3]);
403
- const rot = num(rules[cursor + 4]);
512
+ const key = textureKey(caller, tex) ?? NULL_KEY;
513
+ const repeats = tex === '' ? { x: 1, y: 1, z: 0 } : asVec(rules[cursor + 2]);
514
+ const offsets = tex === '' ? zero() : asVec(rules[cursor + 3]);
515
+ const rot = tex === '' ? 0 : num(rules[cursor + 4]);
404
516
  for (const f of faces) {
405
517
  p.faces[f].normal = {
406
- texture: tex,
518
+ texture: key,
407
519
  textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
408
520
  textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
409
521
  textureRotation: rot,
@@ -414,15 +526,17 @@ export function writePrimParam(prim, param, rules, cursor) {
414
526
  case PRIM_SPECULAR: {
415
527
  const faces = facesOf(p, rules[cursor]);
416
528
  const tex = str(rules[cursor + 1]);
417
- const repeats = asVec(rules[cursor + 2]);
418
- const offsets = asVec(rules[cursor + 3]);
419
- const rot = num(rules[cursor + 4]);
420
- const color = asVec(rules[cursor + 5]);
421
- const gloss = num(rules[cursor + 6]) | 0;
422
- const env = num(rules[cursor + 7]) | 0;
529
+ const key = textureKey(caller, tex) ?? NULL_KEY;
530
+ const reset = tex === '';
531
+ const repeats = reset ? { x: 1, y: 1, z: 0 } : asVec(rules[cursor + 2]);
532
+ const offsets = reset ? zero() : asVec(rules[cursor + 3]);
533
+ const rot = reset ? 0 : num(rules[cursor + 4]);
534
+ const color = reset ? one() : asVec(rules[cursor + 5]);
535
+ const gloss = reset ? 51 : clamp(num(rules[cursor + 6]) | 0, 0, 255);
536
+ const env = reset ? 0 : clamp(num(rules[cursor + 7]) | 0, 0, 255);
423
537
  for (const f of faces) {
424
538
  p.faces[f].specular = {
425
- texture: tex,
539
+ texture: key,
426
540
  textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
427
541
  textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
428
542
  // Second Life stores a negative rotation as 0 and the colour in 8 bits per channel
@@ -436,99 +550,66 @@ export function writePrimParam(prim, param, rules, cursor) {
436
550
  }
437
551
  case PRIM_ALPHA_MODE: {
438
552
  const faces = facesOf(p, rules[cursor]);
439
- const mode = num(rules[cursor + 1]) | 0;
440
- const cutoff = num(rules[cursor + 2]) | 0;
553
+ const mode = clamp(num(rules[cursor + 1]) | 0, 0, 3);
554
+ const cutoff = mode === PRIM_ALPHA_MODE_MASK ? clamp(num(rules[cursor + 2]) | 0, 0, 255) : 0;
441
555
  for (const f of faces) {
442
556
  p.faces[f].alphaMode = { mode, cutoff };
443
557
  }
444
558
  return 3;
445
559
  }
446
560
  case PRIM_GLTF_BASE_COLOR: {
447
- const faces = facesOf(p, rules[cursor]);
448
- const tex = str(rules[cursor + 1]);
449
- const repeats = asVec(rules[cursor + 2]);
450
- const offsets = asVec(rules[cursor + 3]);
451
- const rot = num(rules[cursor + 4]);
452
- const color = asVec(rules[cursor + 5]);
453
- const alpha = num(rules[cursor + 6], 1);
454
- const aMode = num(rules[cursor + 7]) | 0;
455
- const aCut = num(rules[cursor + 8]);
456
- const dbl = num(rules[cursor + 9]) | 0;
457
- for (const f of faces) {
458
- p.faces[f].gltf.baseColor = {
459
- texture: tex,
460
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
461
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
462
- textureRotation: rot,
463
- color: { x: color.x, y: color.y, z: color.z },
464
- alpha,
465
- alphaMode: aMode,
466
- alphaCutoff: aCut,
467
- doubleSided: dbl,
468
- };
469
- }
561
+ const color = rules[cursor + 5];
562
+ const alpha = rules[cursor + 6];
563
+ const factor = color !== '' || alpha !== '';
564
+ writeGltf(prim, caller, rules, cursor, 'baseColor', {
565
+ color: factor ? (color === '' ? one() : clampVec(asVec(color), 0, 1)) : '',
566
+ alpha: factor ? clamp(num(alpha, 1), 0, 1) : '',
567
+ alphaMode: valueOverride(rules[cursor + 7], (v) => clamp(num(v) | 0, 0, 2)),
568
+ alphaCutoff: valueOverride(rules[cursor + 8], (v) => clamp(num(v), 0, 1)),
569
+ doubleSided: valueOverride(rules[cursor + 9], (v) => clamp(num(v) | 0, 0, 1)),
570
+ });
470
571
  return 10;
471
572
  }
472
- case PRIM_GLTF_NORMAL: {
473
- const faces = facesOf(p, rules[cursor]);
474
- const tex = str(rules[cursor + 1]);
475
- const repeats = asVec(rules[cursor + 2]);
476
- const offsets = asVec(rules[cursor + 3]);
477
- const rot = num(rules[cursor + 4]);
478
- for (const f of faces) {
479
- p.faces[f].gltf.normal = {
480
- texture: tex,
481
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
482
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
483
- textureRotation: rot,
484
- };
485
- }
573
+ case PRIM_GLTF_NORMAL:
574
+ writeGltf(prim, caller, rules, cursor, 'normal', {});
486
575
  return 5;
487
- }
488
- case PRIM_GLTF_METALLIC_ROUGHNESS: {
489
- const faces = facesOf(p, rules[cursor]);
490
- const tex = str(rules[cursor + 1]);
491
- const repeats = asVec(rules[cursor + 2]);
492
- const offsets = asVec(rules[cursor + 3]);
493
- const rot = num(rules[cursor + 4]);
494
- const metallic = num(rules[cursor + 5]);
495
- const rough = num(rules[cursor + 6]);
496
- for (const f of faces) {
497
- p.faces[f].gltf.metallicRoughness = {
498
- texture: tex,
499
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
500
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
501
- textureRotation: rot,
502
- metallic,
503
- roughness: rough,
504
- };
505
- }
576
+ case PRIM_GLTF_METALLIC_ROUGHNESS:
577
+ writeGltf(prim, caller, rules, cursor, 'metallicRoughness', {
578
+ metallic: valueOverride(rules[cursor + 5], (v) => clamp(num(v), 0, 1)),
579
+ roughness: valueOverride(rules[cursor + 6], (v) => clamp(num(v), 0, 1)),
580
+ });
506
581
  return 7;
507
- }
508
- case PRIM_GLTF_EMISSIVE: {
509
- const faces = facesOf(p, rules[cursor]);
510
- const tex = str(rules[cursor + 1]);
511
- const repeats = asVec(rules[cursor + 2]);
512
- const offsets = asVec(rules[cursor + 3]);
513
- const rot = num(rules[cursor + 4]);
514
- const tint = asVec(rules[cursor + 5]);
515
- for (const f of faces) {
516
- p.faces[f].gltf.emissive = {
517
- texture: tex,
518
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
519
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
520
- textureRotation: rot,
521
- tint: { x: tint.x, y: tint.y, z: tint.z },
522
- };
523
- }
582
+ case PRIM_GLTF_EMISSIVE:
583
+ writeGltf(prim, caller, rules, cursor, 'emissive', { tint: valueOverride(rules[cursor + 5], (v) => clampVec(asVec(v), 0, 1)) });
524
584
  return 6;
525
- }
526
585
  case PRIM_LINK_TARGET:
527
586
  return null;
528
587
  default:
529
588
  return null;
530
589
  }
531
590
  }
591
+ const valueOverride = (v, read) => (v === '' ? '' : read(v));
592
+ /**
593
+ * Write one PRIM_GLTF_* rule. "" in a transform slot resets it; "" in a texture
594
+ * or value slot unsets that override. Faces without a render material keep
595
+ * only the transforms.
596
+ */
597
+ function writeGltf(prim, caller, rules, cursor, rule, values) {
598
+ const transform = {
599
+ textureRepeats: rules[cursor + 2] === '' ? { x: 1, y: 1, z: 0 } : asVec(rules[cursor + 2]),
600
+ textureOffsets: asVec(rules[cursor + 3]),
601
+ textureRotation: num(rules[cursor + 4]),
602
+ };
603
+ const name = str(rules[cursor + 1]);
604
+ const texture = name === '' ? '' : (textureKey(caller, name) ?? NULL_KEY);
605
+ for (const f of facesOf(prim.params, rules[cursor])) {
606
+ const face = prim.params.faces[f];
607
+ const overrides = face.renderMaterial === NULL_KEY ? {} : { texture, ...values };
608
+ face.gltf[rule] = { ...face.gltf[rule], ...transform, ...overrides };
609
+ }
610
+ }
611
+ /** Names and descriptions can't hold the separator characters; they become `?`. */
612
+ const plainText = (s) => s.replace(/[|\n]/g, '?');
532
613
  function writePrimType(p, rules, cursor) {
533
614
  const kind = (num(rules[cursor]) | 0);
534
615
  const old = p.shape;
@@ -606,7 +687,7 @@ export function readPrimParam(prim, param, rules, cursor) {
606
687
  return { values: [prim.description], consumed: 0 };
607
688
  case PRIM_TEXT: {
608
689
  const t = prim.appearance.text;
609
- return { values: t ? [t.text, t.color, t.alpha] : ['', zero(), 0], consumed: 0 };
690
+ return { values: t ? [t.text, t.color, t.alpha] : ['', zero(), 1], consumed: 0 };
610
691
  }
611
692
  case PRIM_POSITION:
612
693
  return { values: [prim.regionPosition()], consumed: 0 };
@@ -683,7 +764,7 @@ export function readPrimParam(prim, param, rules, cursor) {
683
764
  const out = [];
684
765
  for (const f of faces) {
685
766
  const fa = p.faces[f];
686
- out.push(fa.texture, fa.textureRepeats, fa.textureOffsets, fa.textureRotation);
767
+ out.push(textureName(prim, fa.texture), fa.textureRepeats, fa.textureOffsets, fa.textureRotation);
687
768
  }
688
769
  return { values: out, consumed: 1 };
689
770
  }
@@ -691,7 +772,7 @@ export function readPrimParam(prim, param, rules, cursor) {
691
772
  const faces = facesOf(p, rules[cursor]);
692
773
  const out = [];
693
774
  for (const f of faces) {
694
- out.push(p.faces[f].renderMaterial);
775
+ out.push(materialName(prim, p.faces[f].renderMaterial));
695
776
  }
696
777
  return { values: out, consumed: 1 };
697
778
  }
@@ -732,7 +813,7 @@ export function readPrimParam(prim, param, rules, cursor) {
732
813
  const out = [];
733
814
  for (const f of faces) {
734
815
  const n = p.faces[f].normal;
735
- out.push(n.texture, n.textureRepeats, n.textureOffsets, n.textureRotation);
816
+ out.push(textureName(prim, n.texture), n.textureRepeats, n.textureOffsets, n.textureRotation);
736
817
  }
737
818
  return { values: out, consumed: 1 };
738
819
  }
@@ -741,7 +822,7 @@ export function readPrimParam(prim, param, rules, cursor) {
741
822
  const out = [];
742
823
  for (const f of faces) {
743
824
  const s = p.faces[f].specular;
744
- out.push(s.texture, s.textureRepeats, s.textureOffsets, s.textureRotation, s.color, s.glossiness, s.environment);
825
+ out.push(textureName(prim, s.texture), s.textureRepeats, s.textureOffsets, s.textureRotation, s.color, s.glossiness, s.environment);
745
826
  }
746
827
  return { values: out, consumed: 1 };
747
828
  }
@@ -812,6 +893,7 @@ function readPrimType(p) {
812
893
  return [];
813
894
  }
814
895
  const MATERIAL_TYPES = ['string', 'vector', 'vector', 'float'];
896
+ const MAP_TYPES = ['key', 'vector', 'vector', 'float'];
815
897
  /**
816
898
  * LSL types of the values each PRIM_* rule reads. Per-face rules list one
817
899
  * face's values; the pattern repeats for each face.
@@ -851,13 +933,13 @@ const PRIM_PARAM_TYPES = {
851
933
  [PRIM_GLOW]: ['float'],
852
934
  [PRIM_BUMP_SHINY]: ['integer', 'integer'],
853
935
  [PRIM_TEXGEN]: ['integer'],
854
- [PRIM_NORMAL]: MATERIAL_TYPES,
855
- [PRIM_SPECULAR]: [...MATERIAL_TYPES, 'vector', 'integer', 'integer'],
936
+ [PRIM_NORMAL]: MAP_TYPES,
937
+ [PRIM_SPECULAR]: [...MAP_TYPES, 'vector', 'integer', 'integer'],
856
938
  [PRIM_ALPHA_MODE]: ['integer', 'integer'],
857
- [PRIM_GLTF_BASE_COLOR]: [...MATERIAL_TYPES, 'vector', 'float', 'integer', 'float', 'integer'],
858
- [PRIM_GLTF_NORMAL]: MATERIAL_TYPES,
859
- [PRIM_GLTF_METALLIC_ROUGHNESS]: [...MATERIAL_TYPES, 'float', 'float'],
860
- [PRIM_GLTF_EMISSIVE]: [...MATERIAL_TYPES, 'vector'],
939
+ [PRIM_GLTF_BASE_COLOR]: [...MAP_TYPES, 'vector', 'float', 'integer', 'float', 'integer'],
940
+ [PRIM_GLTF_NORMAL]: MAP_TYPES,
941
+ [PRIM_GLTF_METALLIC_ROUGHNESS]: [...MAP_TYPES, 'float', 'float'],
942
+ [PRIM_GLTF_EMISSIVE]: [...MAP_TYPES, 'vector'],
861
943
  };
862
944
  /** LSL types of PRIM_TYPE's values, which depend on the shape. */
863
945
  function primTypeTypes(kind) {
@@ -871,10 +953,12 @@ function primTypeTypes(kind) {
871
953
  }
872
954
  return [...common, 'vector', 'vector'];
873
955
  }
956
+ /** GLTF override rules take "" in any slot after the face to mean "no override", and read an unset one as "". */
957
+ const GLTF_RULES = new Set([PRIM_GLTF_BASE_COLOR, PRIM_GLTF_NORMAL, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_EMISSIVE]);
874
958
  /** LSL types of the values `readPrimParam` returns for `param`. */
875
959
  export function primParamTypes(param, values) {
876
960
  const pattern = param === PRIM_TYPE ? primTypeTypes(values[0]) : (PRIM_PARAM_TYPES[param] ?? []);
877
- return values.map((_, i) => pattern[i % pattern.length]);
961
+ return values.map((v, i) => (GLTF_RULES.has(param) && v === '' ? 'string' : pattern[i % pattern.length]));
878
962
  }
879
963
  const PER_FACE_RULES = new Set([
880
964
  PRIM_COLOR, PRIM_TEXTURE, PRIM_RENDER_MATERIAL, PRIM_FULLBRIGHT, PRIM_GLOW, PRIM_BUMP_SHINY,
@@ -921,12 +1005,12 @@ const formatG = (v) => String(Number(v.toPrecision(6)));
921
1005
  * the rule can run, otherwise the error text that follows "rule #N" in the
922
1006
  * simulator's message (e.g. " (PRIM_SIZE): arg #1 vector expected but string given").
923
1007
  */
924
- /** GLTF override rules take "" in any slot after the face to mean "no override". */
925
- const GLTF_RULES = new Set([PRIM_GLTF_BASE_COLOR, PRIM_GLTF_NORMAL, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_EMISSIVE]);
926
1008
  export function checkPrimRule(param, rules, cursor) {
927
1009
  const name = RULE_NAMES.get(param);
928
- if (name === undefined || writePrimParamSlots(param, rules, cursor) === null)
1010
+ if (name === undefined)
929
1011
  return ': unknown rule';
1012
+ if (writePrimParamSlots(param, rules, cursor) === null)
1013
+ return param === PRIM_TYPE ? null : ': unknown rule';
930
1014
  const pattern = param === PRIM_TYPE ? primTypeTypes(rules[cursor]) : PRIM_PARAM_TYPES[param];
931
1015
  const expected = PER_FACE_RULES.has(param) ? ['integer', ...pattern] : pattern;
932
1016
  for (let i = 0; i < expected.length; i++) {