@hatiolab/figure-model 0.1.91 → 0.1.93

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 (296) hide show
  1. package/CHANGELOG.md +12 -12
  2. package/README.md +25 -25
  3. package/dist/{v3-asset-types.d.ts → asset-types.d.ts} +67 -67
  4. package/dist/asset-types.d.ts.map +1 -0
  5. package/dist/asset-types.js +2 -0
  6. package/dist/asset-types.js.map +1 -0
  7. package/dist/asset.d.ts +8 -0
  8. package/dist/asset.d.ts.map +1 -0
  9. package/dist/{v3-asset.js → asset.js} +23 -23
  10. package/dist/asset.js.map +1 -0
  11. package/dist/{v3-authoring-actions.d.ts → authoring-actions.d.ts} +66 -66
  12. package/dist/authoring-actions.d.ts.map +1 -0
  13. package/dist/{v3-authoring-actions.js → authoring-actions.js} +107 -78
  14. package/dist/authoring-actions.js.map +1 -0
  15. package/dist/{v3-authoring-session.d.ts → authoring-session.d.ts} +21 -21
  16. package/dist/authoring-session.d.ts.map +1 -0
  17. package/dist/{v3-authoring-session.js → authoring-session.js} +4 -4
  18. package/dist/authoring-session.js.map +1 -0
  19. package/dist/capabilities.d.ts +3 -0
  20. package/dist/capabilities.d.ts.map +1 -0
  21. package/dist/{v3-capabilities.js → capabilities.js} +8 -8
  22. package/dist/capabilities.js.map +1 -0
  23. package/dist/contract.d.ts +7 -0
  24. package/dist/contract.d.ts.map +1 -0
  25. package/dist/contract.js +12 -0
  26. package/dist/contract.js.map +1 -0
  27. package/dist/{v3-cost.d.ts → cost.d.ts} +5 -5
  28. package/dist/cost.d.ts.map +1 -0
  29. package/dist/{v3-cost.js → cost.js} +11 -11
  30. package/dist/cost.js.map +1 -0
  31. package/dist/{v3-create.d.ts → create.d.ts} +8 -8
  32. package/dist/{v3-create.d.ts.map → create.d.ts.map} +1 -1
  33. package/dist/{v3-create.js → create.js} +26 -26
  34. package/dist/create.js.map +1 -0
  35. package/dist/document.d.ts +5 -0
  36. package/dist/document.d.ts.map +1 -0
  37. package/dist/document.js +31 -0
  38. package/dist/document.js.map +1 -0
  39. package/dist/{v3-driver.d.ts → driver.d.ts} +10 -10
  40. package/dist/driver.d.ts.map +1 -0
  41. package/dist/{v3-driver.js → driver.js} +10 -10
  42. package/dist/driver.js.map +1 -0
  43. package/dist/{v3-editor-workspace.d.ts → editor-workspace.d.ts} +73 -73
  44. package/dist/editor-workspace.d.ts.map +1 -0
  45. package/dist/{v3-editor-workspace.js → editor-workspace.js} +29 -29
  46. package/dist/editor-workspace.js.map +1 -0
  47. package/dist/{v3-examples.d.ts → examples.d.ts} +5 -5
  48. package/dist/examples.d.ts.map +1 -0
  49. package/dist/{v3-examples.js → examples.js} +11 -11
  50. package/dist/examples.js.map +1 -0
  51. package/dist/{v3-follow.d.ts → follow.d.ts} +5 -5
  52. package/dist/follow.d.ts.map +1 -0
  53. package/dist/{v3-follow.js → follow.js} +2 -2
  54. package/dist/follow.js.map +1 -0
  55. package/dist/{v3-gate.d.ts → gate.d.ts} +28 -28
  56. package/dist/gate.d.ts.map +1 -0
  57. package/dist/{v3-gate.js → gate.js} +42 -42
  58. package/dist/gate.js.map +1 -0
  59. package/dist/{v3-graph-types.d.ts → graph-types.d.ts} +33 -33
  60. package/dist/graph-types.d.ts.map +1 -0
  61. package/dist/graph-types.js +2 -0
  62. package/dist/graph-types.js.map +1 -0
  63. package/dist/{v3-graph.d.ts → graph.d.ts} +14 -14
  64. package/dist/graph.d.ts.map +1 -0
  65. package/dist/{v3-graph.js → graph.js} +16 -16
  66. package/dist/graph.js.map +1 -0
  67. package/dist/index.d.ts +33 -33
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +33 -33
  70. package/dist/index.js.map +1 -1
  71. package/dist/{v3-instance-size.d.ts → instance-size.d.ts} +14 -14
  72. package/dist/instance-size.d.ts.map +1 -0
  73. package/dist/{v3-instance-size.js → instance-size.js} +13 -13
  74. package/dist/instance-size.js.map +1 -0
  75. package/dist/json.d.ts +12 -0
  76. package/dist/json.d.ts.map +1 -0
  77. package/dist/{v3-json.js → json.js} +13 -13
  78. package/dist/json.js.map +1 -0
  79. package/dist/kernel-version.d.ts +8 -0
  80. package/dist/kernel-version.d.ts.map +1 -0
  81. package/dist/{v3-kernel-version.js → kernel-version.js} +4 -4
  82. package/dist/kernel-version.js.map +1 -0
  83. package/dist/{v3-part-edit.d.ts → part-edit.d.ts} +10 -10
  84. package/dist/part-edit.d.ts.map +1 -0
  85. package/dist/{v3-part-edit.js → part-edit.js} +65 -65
  86. package/dist/part-edit.js.map +1 -0
  87. package/dist/proposal.d.ts +42 -0
  88. package/dist/proposal.d.ts.map +1 -0
  89. package/dist/{v3-proposal.js → proposal.js} +21 -21
  90. package/dist/proposal.js.map +1 -0
  91. package/dist/{v3-public-inputs.d.ts → public-inputs.d.ts} +4 -4
  92. package/dist/public-inputs.d.ts.map +1 -0
  93. package/dist/{v3-public-inputs.js → public-inputs.js} +3 -3
  94. package/dist/public-inputs.js.map +1 -0
  95. package/dist/recipe-build.d.ts +27 -0
  96. package/dist/recipe-build.d.ts.map +1 -0
  97. package/dist/{v3-recipe-build.js → recipe-build.js} +9 -9
  98. package/dist/recipe-build.js.map +1 -0
  99. package/dist/recipe-from-parts.d.ts +8 -0
  100. package/dist/recipe-from-parts.d.ts.map +1 -0
  101. package/dist/{v3-recipe-from-parts.js → recipe-from-parts.js} +9 -9
  102. package/dist/recipe-from-parts.js.map +1 -0
  103. package/dist/{v3-recipe.d.ts → recipe.d.ts} +25 -25
  104. package/dist/recipe.d.ts.map +1 -0
  105. package/dist/{v3-recipe.js → recipe.js} +30 -30
  106. package/dist/recipe.js.map +1 -0
  107. package/dist/release-contract.d.ts +19 -0
  108. package/dist/release-contract.d.ts.map +1 -0
  109. package/dist/{v3-release-contract.js → release-contract.js} +5 -5
  110. package/dist/release-contract.js.map +1 -0
  111. package/dist/{v3-render-batches.d.ts → render-batches.d.ts} +8 -8
  112. package/dist/render-batches.d.ts.map +1 -0
  113. package/dist/{v3-render-batches.js → render-batches.js} +4 -4
  114. package/dist/render-batches.js.map +1 -0
  115. package/dist/{v3-score.d.ts → score.d.ts} +19 -19
  116. package/dist/score.d.ts.map +1 -0
  117. package/dist/{v3-score.js → score.js} +32 -32
  118. package/dist/score.js.map +1 -0
  119. package/dist/{v3-shape-sampling.d.ts → shape-sampling.d.ts} +4 -4
  120. package/dist/shape-sampling.d.ts.map +1 -0
  121. package/dist/{v3-shape-sampling.js → shape-sampling.js} +3 -3
  122. package/dist/shape-sampling.js.map +1 -0
  123. package/dist/{v3-surface.d.ts → surface.d.ts} +5 -5
  124. package/dist/surface.d.ts.map +1 -0
  125. package/dist/{v3-surface.js → surface.js} +7 -7
  126. package/dist/surface.js.map +1 -0
  127. package/dist/{v3-words.d.ts → words.d.ts} +3 -3
  128. package/dist/words.d.ts.map +1 -0
  129. package/dist/{v3-words.js → words.js} +3 -3
  130. package/dist/words.js.map +1 -0
  131. package/docs/{assembly-constraints-v3.md → assembly-constraints.md} +1 -1
  132. package/docs/{v3-asset-persistence.md → asset-persistence.md} +8 -8
  133. package/docs/{v3-authoring-transactions.md → authoring-transactions.md} +2 -2
  134. package/docs/{v3-capability-bindings.md → capability-bindings.md} +8 -8
  135. package/docs/{v3-common-kernel-experiment.md → common-kernel-experiment.md} +4 -4
  136. package/docs/{v3-core-semantics.md → core-semantics.md} +5 -5
  137. package/docs/{v3-cutover.md → cutover.md} +4 -4
  138. package/docs/{v3-design.md → design.md} +11 -11
  139. package/docs/{v3-document-contract.md → document-contract.md} +9 -9
  140. package/docs/{v3-dryroom-profile-validation.md → dryroom-profile-validation.md} +4 -4
  141. package/docs/{v3-editor-integration.md → editor-integration.md} +6 -6
  142. package/docs/{v3-full-conveyor-validation.md → full-conveyor-validation.md} +4 -4
  143. package/docs/{v3-full-dryroom-validation.md → full-dryroom-validation.md} +4 -4
  144. package/docs/{v3-full-forklift-validation.md → full-forklift-validation.md} +5 -5
  145. package/docs/{v3-full-robot-validation.md → full-robot-validation.md} +4 -4
  146. package/docs/{v3-layout-system.md → layout-system.md} +4 -4
  147. package/docs/{v3-legacy-risk-audit-2026-09-23.md → legacy-risk-audit-2026-09-23.md} +5 -5
  148. package/docs/{v3-minimal-model.md → minimal-model.md} +6 -6
  149. package/docs/{v3-motion-contract.md → motion-contract.md} +3 -3
  150. package/docs/{v3-openusd-review.md → openusd-review.md} +8 -8
  151. package/docs/{v3-operator-contracts.md → operator-contracts.md} +6 -6
  152. package/docs/{v3-payload-collision-validation.md → payload-collision-validation.md} +4 -4
  153. package/docs/{v3-payload-contact-validation.md → payload-contact-validation.md} +5 -5
  154. package/docs/prototypes/README.md +10 -10
  155. package/docs/prototypes/{v3-asset-samples.mjs → asset-samples.mjs} +6 -6
  156. package/docs/prototypes/{v3-asset-store.mjs → asset-store.mjs} +3 -3
  157. package/docs/prototypes/{v3-asset-store.test.mjs → asset-store.test.mjs} +6 -6
  158. package/docs/prototypes/asset.mjs +1 -0
  159. package/docs/prototypes/{v3-asset.schema.json → asset.schema.json} +2 -2
  160. package/docs/prototypes/{v3-asset.test.mjs → asset.test.mjs} +10 -10
  161. package/docs/prototypes/authoring-session.mjs +1 -0
  162. package/docs/prototypes/{v3-authoring-session.test.mjs → authoring-session.test.mjs} +6 -6
  163. package/docs/prototypes/capability-plan.mjs +1 -0
  164. package/docs/prototypes/{v3-capability-plan.test.mjs → capability-plan.test.mjs} +5 -5
  165. package/docs/prototypes/{v3-capability-samples.mjs → capability-samples.mjs} +2 -2
  166. package/docs/prototypes/document.mjs +2 -0
  167. package/docs/prototypes/{v3-document.schema.json → document.schema.json} +1 -1
  168. package/docs/prototypes/{v3-document.test.mjs → document.test.mjs} +7 -7
  169. package/docs/prototypes/{v3-dryroom-mesh.mjs → dryroom-mesh.mjs} +1 -1
  170. package/docs/prototypes/{v3-dryroom-mesh.test.mjs → dryroom-mesh.test.mjs} +1 -1
  171. package/docs/prototypes/{v3-editor-browser.mjs → editor-browser.mjs} +13 -13
  172. package/docs/prototypes/editor-workspace.mjs +1 -0
  173. package/docs/prototypes/{v3-editor-workspace.test.mjs → editor-workspace.test.mjs} +3 -3
  174. package/docs/prototypes/{v3-feature-pattern.test.mjs → feature-pattern.test.mjs} +1 -1
  175. package/docs/prototypes/{v3-full-conveyor.mjs → full-conveyor.mjs} +2 -2
  176. package/docs/prototypes/{v3-full-conveyor.test.mjs → full-conveyor.test.mjs} +3 -3
  177. package/docs/prototypes/{v3-full-dryroom.mjs → full-dryroom.mjs} +2 -2
  178. package/docs/prototypes/{v3-full-dryroom.test.mjs → full-dryroom.test.mjs} +3 -3
  179. package/docs/prototypes/{v3-full-forklift.mjs → full-forklift.mjs} +2 -2
  180. package/docs/prototypes/{v3-full-forklift.test.mjs → full-forklift.test.mjs} +3 -3
  181. package/docs/prototypes/{v3-full-robot.mjs → full-robot.mjs} +2 -2
  182. package/docs/prototypes/{v3-full-robot.test.mjs → full-robot.test.mjs} +4 -4
  183. package/docs/prototypes/{v3-kernel.test.mjs → kernel.test.mjs} +1 -1
  184. package/docs/prototypes/{v3-layout.test.mjs → layout.test.mjs} +1 -1
  185. package/docs/prototypes/{v3-more-samples.test.mjs → more-samples.test.mjs} +2 -2
  186. package/docs/prototypes/{v3-payload-collision.mjs → payload-collision.mjs} +2 -2
  187. package/docs/prototypes/{v3-payload-collision.test.mjs → payload-collision.test.mjs} +3 -3
  188. package/docs/prototypes/{v3-payload-fit.mjs → payload-fit.mjs} +2 -2
  189. package/docs/prototypes/{v3-payload-fit.test.mjs → payload-fit.test.mjs} +4 -4
  190. package/docs/prototypes/render-full-dryroom.mjs +12 -12
  191. package/docs/prototypes/{v3-repeated-binding.mjs → repeated-binding.mjs} +1 -1
  192. package/docs/prototypes/{v3-repeated-binding.test.mjs → repeated-binding.test.mjs} +6 -6
  193. package/docs/prototypes/{v3-runtime-browser.mjs → runtime-browser.mjs} +26 -26
  194. package/docs/prototypes/{v3-samples.test.mjs → samples.test.mjs} +1 -1
  195. package/docs/prototypes/{v3-spatial-plan.test.mjs → spatial-plan.test.mjs} +1 -1
  196. package/docs/prototypes/typed-plan.mjs +3 -0
  197. package/docs/prototypes/{v3-typed-plan.test.mjs → typed-plan.test.mjs} +1 -1
  198. package/docs/prototypes/{v3-typed-repeat.test.mjs → typed-repeat.test.mjs} +2 -2
  199. package/docs/{v3-repeated-binding-validation.md → repeated-binding-validation.md} +4 -4
  200. package/docs/{v3-research-review.md → research-review.md} +2 -2
  201. package/docs/{v3-review-2026-09-22.md → review-2026-09-22.md} +10 -10
  202. package/docs/{v3-runtime-extraction.md → runtime-extraction.md} +16 -16
  203. package/docs/{v3-sample-validation-expanded.md → sample-validation-expanded.md} +5 -5
  204. package/docs/{v3-sample-validation.md → sample-validation.md} +3 -3
  205. package/docs/{v3-shape-dimension-contract.md → shape-dimension-contract.md} +12 -12
  206. package/docs/{v3-shapeassembly-reproduction.md → shapeassembly-reproduction.md} +4 -4
  207. package/docs/{v3-spatial-contracts.md → spatial-contracts.md} +5 -5
  208. package/docs/status.md +12 -0
  209. package/docs/{v3-storage-integration.md → storage-integration.md} +5 -5
  210. package/docs/{v3-typed-repeat-contracts.md → typed-repeat-contracts.md} +3 -3
  211. package/package.json +3 -3
  212. package/dist/v3-asset-types.d.ts.map +0 -1
  213. package/dist/v3-asset-types.js +0 -2
  214. package/dist/v3-asset-types.js.map +0 -1
  215. package/dist/v3-asset.d.ts +0 -8
  216. package/dist/v3-asset.d.ts.map +0 -1
  217. package/dist/v3-asset.js.map +0 -1
  218. package/dist/v3-authoring-actions.d.ts.map +0 -1
  219. package/dist/v3-authoring-actions.js.map +0 -1
  220. package/dist/v3-authoring-session.d.ts.map +0 -1
  221. package/dist/v3-authoring-session.js.map +0 -1
  222. package/dist/v3-capabilities.d.ts +0 -3
  223. package/dist/v3-capabilities.d.ts.map +0 -1
  224. package/dist/v3-capabilities.js.map +0 -1
  225. package/dist/v3-contract.d.ts +0 -7
  226. package/dist/v3-contract.d.ts.map +0 -1
  227. package/dist/v3-contract.js +0 -12
  228. package/dist/v3-contract.js.map +0 -1
  229. package/dist/v3-cost.d.ts.map +0 -1
  230. package/dist/v3-cost.js.map +0 -1
  231. package/dist/v3-create.js.map +0 -1
  232. package/dist/v3-document.d.ts +0 -5
  233. package/dist/v3-document.d.ts.map +0 -1
  234. package/dist/v3-document.js +0 -31
  235. package/dist/v3-document.js.map +0 -1
  236. package/dist/v3-driver.d.ts.map +0 -1
  237. package/dist/v3-driver.js.map +0 -1
  238. package/dist/v3-editor-workspace.d.ts.map +0 -1
  239. package/dist/v3-editor-workspace.js.map +0 -1
  240. package/dist/v3-examples.d.ts.map +0 -1
  241. package/dist/v3-examples.js.map +0 -1
  242. package/dist/v3-follow.d.ts.map +0 -1
  243. package/dist/v3-follow.js.map +0 -1
  244. package/dist/v3-gate.d.ts.map +0 -1
  245. package/dist/v3-gate.js.map +0 -1
  246. package/dist/v3-graph-types.d.ts.map +0 -1
  247. package/dist/v3-graph-types.js +0 -2
  248. package/dist/v3-graph-types.js.map +0 -1
  249. package/dist/v3-graph.d.ts.map +0 -1
  250. package/dist/v3-graph.js.map +0 -1
  251. package/dist/v3-instance-size.d.ts.map +0 -1
  252. package/dist/v3-instance-size.js.map +0 -1
  253. package/dist/v3-json.d.ts +0 -12
  254. package/dist/v3-json.d.ts.map +0 -1
  255. package/dist/v3-json.js.map +0 -1
  256. package/dist/v3-kernel-version.d.ts +0 -8
  257. package/dist/v3-kernel-version.d.ts.map +0 -1
  258. package/dist/v3-kernel-version.js.map +0 -1
  259. package/dist/v3-part-edit.d.ts.map +0 -1
  260. package/dist/v3-part-edit.js.map +0 -1
  261. package/dist/v3-proposal.d.ts +0 -42
  262. package/dist/v3-proposal.d.ts.map +0 -1
  263. package/dist/v3-proposal.js.map +0 -1
  264. package/dist/v3-public-inputs.d.ts.map +0 -1
  265. package/dist/v3-public-inputs.js.map +0 -1
  266. package/dist/v3-recipe-build.d.ts +0 -27
  267. package/dist/v3-recipe-build.d.ts.map +0 -1
  268. package/dist/v3-recipe-build.js.map +0 -1
  269. package/dist/v3-recipe-from-parts.d.ts +0 -8
  270. package/dist/v3-recipe-from-parts.d.ts.map +0 -1
  271. package/dist/v3-recipe-from-parts.js.map +0 -1
  272. package/dist/v3-recipe.d.ts.map +0 -1
  273. package/dist/v3-recipe.js.map +0 -1
  274. package/dist/v3-release-contract.d.ts +0 -19
  275. package/dist/v3-release-contract.d.ts.map +0 -1
  276. package/dist/v3-release-contract.js.map +0 -1
  277. package/dist/v3-render-batches.d.ts.map +0 -1
  278. package/dist/v3-render-batches.js.map +0 -1
  279. package/dist/v3-score.d.ts.map +0 -1
  280. package/dist/v3-score.js.map +0 -1
  281. package/dist/v3-shape-sampling.d.ts.map +0 -1
  282. package/dist/v3-shape-sampling.js.map +0 -1
  283. package/dist/v3-surface.d.ts.map +0 -1
  284. package/dist/v3-surface.js.map +0 -1
  285. package/dist/v3-words.d.ts.map +0 -1
  286. package/dist/v3-words.js.map +0 -1
  287. package/docs/prototypes/v3-asset.mjs +0 -1
  288. package/docs/prototypes/v3-authoring-session.mjs +0 -1
  289. package/docs/prototypes/v3-capability-plan.mjs +0 -1
  290. package/docs/prototypes/v3-document.mjs +0 -2
  291. package/docs/prototypes/v3-editor-workspace.mjs +0 -1
  292. package/docs/prototypes/v3-typed-plan.mjs +0 -3
  293. package/docs/v3-status.md +0 -50
  294. /package/docs/prototypes/{v3-kernel.mjs → kernel.mjs} +0 -0
  295. /package/docs/prototypes/{v3-linear-comparison.py → linear-comparison.py} +0 -0
  296. /package/docs/prototypes/{v3-semantics.test.mjs → semantics.test.mjs} +0 -0
@@ -4,7 +4,7 @@
4
4
  /*
5
5
  * What a person is trying to say when they build a figure, as commands.
6
6
  *
7
- * The four things the hand-written sliding gate needed and the authoring API had no word for (V3 designer's
7
+ * The four things the hand-written sliding gate needed and the authoring API had no word for (designer's
8
8
  * ruling 2026-09-23): a dimension that follows something, a part fastened to a face, a part that moves, and the
9
9
  * room the figure takes up. This file carries the first two; the other two follow.
10
10
  *
@@ -13,7 +13,7 @@
13
13
  * a reference an author or a connector holds keeps pointing at the same part after any of these.
14
14
  *
15
15
  * Frames. A part's placement is built from three nodes that are never merged, so adding one does not erase
16
- * another (V3 designer's ruling: attachment and motion must not overwrite the same pose):
16
+ * another (designer's ruling: attachment and motion must not overwrite the same pose):
17
17
  *
18
18
  * <id>.local --<id>.pose--> <id>.seat --<id>.motion--> <parent>.local --<parent>.world--> asset
19
19
  *
@@ -21,25 +21,25 @@
21
21
  * absent until something makes it move. `<parent>.world` is the parent's own chain, so moving or resizing the
22
22
  * parent carries the child with it. A part that is fastened to nothing poses straight into the asset frame.
23
23
  */
24
- import { compileV3Asset } from "./v3-asset.js";
25
- import { checkPublicInput, keepPublicInputs } from "./v3-public-inputs.js";
26
- import { v3PartsExtentOf } from "./v3-gate.js";
27
- import { compileV3Graph, v3ShapeFaces } from "./v3-graph.js";
28
- import { V3ContractError } from "./v3-contract.js";
29
- import { AXES, REPEAT_LIMIT } from "./v3-words.js";
30
- import { defaultInstanceSizeRange, foldInstanceSizeRange, v3InstanceSizeLimitsOf, V3_INSTANCE_SIZE_DEFAULT, V3_INSTANCE_SIZE_INPUTS } from "./v3-instance-size.js";
31
- import { V3_PLACEMENTS } from "./v3-asset-types.js";
24
+ import { compileFigureAsset } from "./asset.js";
25
+ import { checkPublicInput, keepPublicInputs } from "./public-inputs.js";
26
+ import { figurePartsExtentOf } from "./gate.js";
27
+ import { compileFigureGraph, figureShapeFaces } from "./graph.js";
28
+ import { FigureContractError } from "./contract.js";
29
+ import { AXES, REPEAT_LIMIT } from "./words.js";
30
+ import { defaultInstanceSizeRange, foldInstanceSizeRange, figureInstanceSizeLimitsOf, FIGURE_INSTANCE_SIZE_DEFAULT, FIGURE_INSTANCE_SIZE_INPUTS } from "./instance-size.js";
31
+ import { FIGURE_PLACEMENTS } from "./asset-types.js";
32
32
  const fail = (code, path, message) => {
33
- throw new V3ContractError(code, path, message);
33
+ throw new FigureContractError(code, path, message);
34
34
  };
35
35
  /** The six faces of a box-shaped part, and the axis each one faces along. */
36
- export const V3_FACES = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' };
36
+ export const FIGURE_FACES = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' };
37
37
  /**
38
38
  * UsdPhysics's joint types (ADR-0093, amended 2026-09-25: the joint follows USD). A `fixed` joint is a fastening; a
39
39
  * `revolute` joint turns -- with no limits it turns without end, its value a phase (-180..180), as USD's revolute
40
40
  * joint without limits does; a `prismatic` joint slides.
41
41
  */
42
- export const V3_JOINT_TYPES = ['fixed', 'revolute', 'prismatic'];
42
+ export const FIGURE_JOINT_TYPES = ['fixed', 'revolute', 'prismatic'];
43
43
  const SIGN = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 };
44
44
  const OPPOSITE = { left: 'right', right: 'left', bottom: 'top', top: 'bottom', back: 'front', front: 'back' };
45
45
  const clone = (asset) => structuredClone(asset);
@@ -172,7 +172,7 @@ const OUTER = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front']
172
172
  /** A part's declared faces, from its shape's own definition, or a refusal saying the shape has none. */
173
173
  function facesOfPart(m, id, path) {
174
174
  const { shape } = partOf(m, id, path);
175
- const faces = v3ShapeFaces(shape.op);
175
+ const faces = figureShapeFaces(shape.op);
176
176
  if (!faces)
177
177
  fail('EDIT_TARGET', path, `${id} is a ${shape.op}; it has no flat face to fasten by`);
178
178
  return { shape, faces: faces };
@@ -189,12 +189,12 @@ function childFaceOf(m, j) {
189
189
  fail('JOINT_SCHEMA', path, 'a part stands on the mounting plane by a face of its own, not by its centre');
190
190
  /* One of the six, in the record's words: the face across from it. An opening's face, read from the parent's shape. */
191
191
  const named = j.body0.face;
192
- if (Object.hasOwn(V3_FACES, named))
193
- return Object.keys(V3_FACES).find(f => V3_FACES[f] === V3_FACES[named] && SIGN[f] === -SIGN[named]);
192
+ if (Object.hasOwn(FIGURE_FACES, named))
193
+ return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === FIGURE_FACES[named] && SIGN[f] === -SIGN[named]);
194
194
  const theirs = facesOfPart(m, j.body0.part, path).faces[j.body0.face];
195
195
  if (!theirs)
196
196
  fail('JOINT_SCHEMA', path, `${j.body0.part} has no face ${j.body0.face}`);
197
- return Object.keys(V3_FACES).find(f => V3_FACES[f] === theirs.axis && SIGN[f] === -theirs.normal);
197
+ return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === theirs.axis && SIGN[f] === -theirs.normal);
198
198
  }
199
199
  const planeOf = (shape, face) => face.plane.map(p => ({ ref: shape.args[p.arg], k: p.k }));
200
200
  /** The part's outer faces on each axis in its own frame; an axis without a pair of flat faces has none. */
@@ -328,7 +328,7 @@ function sourceRef(m, asset, source, path) {
328
328
  *
329
329
  * "B is as wide as A" has to keep meaning that when A is told to follow something else. Reading A's shape
330
330
  * argument at the moment of linking gave B whatever A happened to read then — A's own control — so relinking A
331
- * left B on the old value (V3 designer's counterexample 2026-09-23: A went to 300 and B stayed at 100). Every
331
+ * left B on the old value (designer's counterexample 2026-09-23: A went to 300 and B stayed at 100). Every
332
332
  * dimension a command touches gets a node of its own, `<part>.dimension.<axis>`, and that is what the shape
333
333
  * reads and what anything following it reads. Relinking rewrites what that node computes, and everyone pointing
334
334
  * at it comes along — fastenings included, since they measure the same references.
@@ -356,7 +356,7 @@ function stableDimension(m, part, axis, path) {
356
356
  const dimensionSource = (m, part, axis) => nodeById(m, `${part}.dimension.${axis}`)?.args?.[0];
357
357
  /** Whether this side of the part already follows something, rather than reading its own control. */
358
358
  /**
359
- * A part's own number for a dimension: a design input named under the part. `addV3Box` names it `<part>.size.<axis>`,
359
+ * A part's own number for a dimension: a design input named under the part. `addFigureBox` names it `<part>.size.<axis>`,
360
360
  * the editor's shape palette `<part>.dimension.width` (and height, depth); both are the part's own, and a dimension
361
361
  * read from one follows nothing. Reading only the first name made a palette box look linked as soon as any command
362
362
  * had routed its dimension, and a link was refused as a replacement (2026-09-24, :3300).
@@ -476,7 +476,7 @@ function attach(asset, a, twist) {
476
476
  if (a.part === to.part)
477
477
  fail('ATTACH_CYCLE', path, 'a part cannot be fastened to itself');
478
478
  const byCentre = a.face === 'centre';
479
- if (!byCentre && !Object.hasOwn(V3_FACES, String(a.face)))
479
+ if (!byCentre && !Object.hasOwn(FIGURE_FACES, String(a.face)))
480
480
  fail('SCHEMA', path, `${String(a.face)} is not a face`);
481
481
  /* The target's faces are the ones its shape declares: a portal's opening as well as its outer faces. */
482
482
  const target = facesOfPart(m, to.part, path);
@@ -492,10 +492,10 @@ function attach(asset, a, twist) {
492
492
  fail('ATTACH_FACE', path, `${a.part} fastened by its centre sits on ${to.face}; flush has no meaning for a centre`);
493
493
  /* By its centre, the part is measured as the face that would meet the target's; only its offset differs. */
494
494
  if (byCentre)
495
- a = { ...a, face: Object.keys(V3_FACES).find(f => V3_FACES[f] === axis && SIGN[f] === -sign) };
496
- if (V3_FACES[a.face] !== axis)
497
- fail('ATTACH_FACE', path, `${a.face} faces along ${V3_FACES[a.face]} and ${to.face} along ${axis}; fastening them would need a turn, which this command does not do`);
498
- const wanted = Object.keys(V3_FACES).find(f => V3_FACES[f] === axis && SIGN[f] === (facing === 'meet' ? -sign : sign));
495
+ a = { ...a, face: Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === -sign) };
496
+ if (FIGURE_FACES[a.face] !== axis)
497
+ fail('ATTACH_FACE', path, `${a.face} faces along ${FIGURE_FACES[a.face]} and ${to.face} along ${axis}; fastening them would need a turn, which this command does not do`);
498
+ const wanted = Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === (facing === 'meet' ? -sign : sign));
499
499
  if (facing === 'meet' && a.face !== wanted)
500
500
  fail('ATTACH_FACE', path, `to meet ${to.face}, ${a.part} offers its ${wanted}; pass flush to lay ${a.face} in the same plane instead`);
501
501
  if (facing === 'flush' && a.face !== wanted)
@@ -645,8 +645,8 @@ function standOnPlane(asset, a, path) {
645
645
  const normal = placement === 'wall' ? 'z' : 'y';
646
646
  if (!takes.includes(a.face))
647
647
  fail('ATTACH_FACE', path, placement === 'wall'
648
- ? `${String(a.face)} faces along ${V3_FACES[a.face] ?? 'no axis'}; the figure hangs on a wall, the plane Z = 0, so a part is fastened to it by its back`
649
- : `${String(a.face)} faces along ${V3_FACES[a.face] ?? 'no axis'}; the plane faces along y, so a part stands on it by its bottom or hangs from it by its top`);
648
+ ? `${String(a.face)} faces along ${FIGURE_FACES[a.face] ?? 'no axis'}; the figure hangs on a wall, the plane Z = 0, so a part is fastened to it by its back`
649
+ : `${String(a.face)} faces along ${FIGURE_FACES[a.face] ?? 'no axis'}; the plane faces along y, so a part stands on it by its bottom or hangs from it by its top`);
650
650
  if (parentOf(m, a.part) && !a.replace)
651
651
  fail('ATTACH_REPLACED', path, `${a.part} is already fastened to ${parentOf(m, a.part)}; pass replace to stand it on the plane instead`);
652
652
  const mine = standingSpansOf(m, asset, a.part, path);
@@ -701,7 +701,7 @@ function detach(asset, a) {
701
701
  /*
702
702
  Detaching keeps the part where it stands, and keeps it moving the way it moved. Taking the world pose and
703
703
  writing it in as the part's own pose did neither: the part's motion was already in that pose and was then
704
- applied a second time, so a part with 20 mm of travel jumped to 40 (V3 designer's counterexample
704
+ applied a second time, so a part with 20 mm of travel jumped to 40 (designer's counterexample
705
705
  2026-09-23).
706
706
 
707
707
  What the part's pose has to gain is exactly what the parent's chain was contributing — and that is a plain
@@ -710,7 +710,7 @@ function detach(asset, a) {
710
710
  */
711
711
  for (let up = parent; up; up = parentOf(m, up))
712
712
  requireUnturned(m, asset, up, path);
713
- const evaluated = compileV3Asset(asset).evaluate();
713
+ const evaluated = compileFigureAsset(asset).evaluate();
714
714
  const above = evaluated.values?.[nodeById(m, `${parent}.world`)?.outputs?.pose ?? nodeById(m, `${parent}.chain`)?.outputs?.pose ?? nodeById(m, `${parent}.pose`).outputs.pose];
715
715
  if (!above || !above.t)
716
716
  fail('TARGET_ABSENT', path, `${parent} has no pose to read`);
@@ -833,8 +833,8 @@ function reseat(asset, id, to, place) {
833
833
  dropIfUnused(asset, ref, ownInput(m, id, ref) ? ref : undefined);
834
834
  }
835
835
  /** Apply one authoring command. Throws on any refusal, leaving the asset it was given untouched. */
836
- export function applyV3Authoring(source, action) {
837
- compileV3Asset(source);
836
+ export function applyFigureAuthoring(source, action) {
837
+ compileFigureAsset(source);
838
838
  if (!action || typeof action !== 'object')
839
839
  fail('SCHEMA', 'action', 'an authoring action is required');
840
840
  /* Made of the commands it stands for, each checked as it would be alone. */
@@ -989,7 +989,7 @@ export function applyV3Authoring(source, action) {
989
989
  rewriteJoints(asset);
990
990
  /* An input a command took away takes its place on the board with it (ADR-0096). */
991
991
  keepPublicInputs(asset);
992
- compileV3Asset(asset);
992
+ compileFigureAsset(asset);
993
993
  if ((action.kind === 'link-dimension' || action.kind === 'make-repeat' || action.kind === 'set-joint') && action.narrowRange)
994
994
  narrowDeclaredSize(asset);
995
995
  holdsOverDeclaredSize(asset, source);
@@ -1020,28 +1020,28 @@ function followSize(source, a) {
1020
1020
  let asset = source;
1021
1021
  const m = asset.document.model;
1022
1022
  if (!AXES.every(axis => sizeInputOf(m, axis))) {
1023
- const at = v3PartsExtentOf(asset);
1023
+ const at = figurePartsExtentOf(asset);
1024
1024
  const start = defaultInstanceSizeRange(at);
1025
- const range = foldInstanceSizeRange(start, v3InstanceSizeLimitsOf(asset, start)).range;
1026
- asset = applyV3Authoring(asset, { kind: 'declare-instance-size', range, at, replace: AXES.some(axis => sizeInputOf(m, axis)) });
1025
+ const range = foldInstanceSizeRange(start, figureInstanceSizeLimitsOf(asset, start)).range;
1026
+ asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range, at, replace: AXES.some(axis => sizeInputOf(m, axis)) });
1027
1027
  }
1028
- const values = compileV3Graph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
1028
+ const values = compileFigureGraph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
1029
1029
  const argsNow = (asset.document.model.nodes.find((n) => n.id === `${a.part}.shape`)).args;
1030
1030
  for (const axis of axes) {
1031
1031
  const measure = values[argsNow[AXES.indexOf(axis)]];
1032
- const size = asset.designInputs[V3_INSTANCE_SIZE_INPUTS[axis]];
1032
+ const size = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
1033
1033
  if (!(typeof measure === 'number' && measure > 0 && typeof size === 'number' && size > 0))
1034
1034
  fail('EDIT_TARGET', `${path}.${axis}`, `${a.part}'s ${axis} or the figure's size is not a length to follow`);
1035
- asset = applyV3Authoring(asset, { kind: 'link-dimension', part: a.part, axis, source: { of: 'instance', axis }, times: measure / size, replace: true, narrowRange: true });
1035
+ asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: a.part, axis, source: { of: 'instance', axis }, times: measure / size, replace: true, narrowRange: true });
1036
1036
  }
1037
1037
  return asset;
1038
1038
  }
1039
- const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id === V3_INSTANCE_SIZE_INPUTS[axis]);
1039
+ const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id === FIGURE_INSTANCE_SIZE_INPUTS[axis]);
1040
1040
  function declareInstanceSize(asset, a) {
1041
1041
  const m = asset.document.model;
1042
1042
  const declared = AXES.filter(axis => sizeInputOf(m, axis));
1043
1043
  if (declared.length && !a.replace)
1044
- fail('INSTANCE_SIZE_DECLARED', 'size', `the instance size is already declared (${declared.map(x => V3_INSTANCE_SIZE_INPUTS[x]).join(', ')}); pass replace to change its range`);
1044
+ fail('INSTANCE_SIZE_DECLARED', 'size', `the instance size is already declared (${declared.map(x => FIGURE_INSTANCE_SIZE_INPUTS[x]).join(', ')}); pass replace to change its range`);
1045
1045
  if (!a.range || typeof a.range !== 'object')
1046
1046
  fail('SCHEMA', 'size', 'a range per axis is required');
1047
1047
  let measured;
@@ -1049,18 +1049,18 @@ function declareInstanceSize(asset, a) {
1049
1049
  const given = a.at?.[axis];
1050
1050
  if (given !== undefined)
1051
1051
  return given;
1052
- const existing = asset.designInputs[V3_INSTANCE_SIZE_INPUTS[axis]];
1052
+ const existing = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
1053
1053
  if (typeof existing === 'number')
1054
1054
  return existing;
1055
1055
  /* The parts as they rest, not the room their joints carry them through (a door's swing). */
1056
1056
  if (!measured)
1057
- measured = v3PartsExtentOf(asset);
1057
+ measured = figurePartsExtentOf(asset);
1058
1058
  return measured[axis];
1059
1059
  };
1060
1060
  for (const axis of AXES) {
1061
1061
  const r = a.range[axis];
1062
1062
  const at = authoredOf(axis);
1063
- const path = V3_INSTANCE_SIZE_INPUTS[axis];
1063
+ const path = FIGURE_INSTANCE_SIZE_INPUTS[axis];
1064
1064
  if (!r || ![r.min, r.max, at].every(Number.isFinite))
1065
1065
  fail('SCHEMA', path, 'finite min, max and size required');
1066
1066
  if (!(r.min > 0))
@@ -1088,7 +1088,7 @@ function sizeRangeGaps(asset) {
1088
1088
  if (!AXES.every(axis => sizeInputOf(m, axis)))
1089
1089
  return [];
1090
1090
  const range = Object.fromEntries(AXES.map(axis => [axis, { min: sizeInputOf(m, axis).min, max: sizeInputOf(m, axis).max }]));
1091
- const limits = v3InstanceSizeLimitsOf(asset, range);
1091
+ const limits = figureInstanceSizeLimitsOf(asset, range);
1092
1092
  const { range: holds } = foldInstanceSizeRange(range, limits);
1093
1093
  const gaps = [];
1094
1094
  for (const axis of AXES) {
@@ -1108,9 +1108,9 @@ function narrowDeclaredSize(asset) {
1108
1108
  return;
1109
1109
  for (const gap of sizeRangeGaps(asset)) {
1110
1110
  const input = sizeInputOf(m, gap.axis);
1111
- const at = asset.designInputs[V3_INSTANCE_SIZE_INPUTS[gap.axis]];
1111
+ const at = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[gap.axis]];
1112
1112
  if (gap.bound === 'min' ? at < gap.need : at > gap.need)
1113
- fail('INSTANCE_SIZE_RANGE', V3_INSTANCE_SIZE_INPUTS[gap.axis], `the figure is authored at ${at} mm, where a length is already gone; change the link or the size`);
1113
+ fail('INSTANCE_SIZE_RANGE', FIGURE_INSTANCE_SIZE_INPUTS[gap.axis], `the figure is authored at ${at} mm, where a length is already gone; change the link or the size`);
1114
1114
  if (gap.bound === 'min')
1115
1115
  input.min = gap.need;
1116
1116
  else
@@ -1133,7 +1133,7 @@ function holdsOverDeclaredSize(asset, before) {
1133
1133
  const worse = !prior || (gap.bound === 'min' ? gap.need > prior.need + 1e-9 || gap.declared < prior.declared : gap.need < prior.need - 1e-9 || gap.declared > prior.declared);
1134
1134
  if (!worse)
1135
1135
  continue;
1136
- const path = V3_INSTANCE_SIZE_INPUTS[gap.axis];
1136
+ const path = FIGURE_INSTANCE_SIZE_INPUTS[gap.axis];
1137
1137
  const by = gap.who ? ` (${gap.who})` : '';
1138
1138
  if (gap.bound === 'min')
1139
1139
  fail('INSTANCE_SIZE_RANGE', path, `below ${round6(gap.need)} mm a length goes to zero${by}; declare the smallest size at ${round6(gap.need)} mm or more`);
@@ -1141,7 +1141,7 @@ function holdsOverDeclaredSize(asset, before) {
1141
1141
  }
1142
1142
  }
1143
1143
  /** What each part is fastened to, for a reader that wants the structure rather than the graph. */
1144
- export function v3AttachmentsOf(asset) {
1144
+ export function figureAttachmentsOf(asset) {
1145
1145
  const m = asset.document.model;
1146
1146
  const out = {};
1147
1147
  for (const n of m.nodes)
@@ -1161,8 +1161,8 @@ function checkJoint(asset, j) {
1161
1161
  const path = `joints.${String(j?.id)}`;
1162
1162
  if (!j || typeof j.id !== 'string' || !j.id.trim())
1163
1163
  fail('JOINT_SCHEMA', 'joints', 'a joint has an id');
1164
- if (!V3_JOINT_TYPES.includes(j.type))
1165
- fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${V3_JOINT_TYPES.join(', ')}`);
1164
+ if (!FIGURE_JOINT_TYPES.includes(j.type))
1165
+ fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${FIGURE_JOINT_TYPES.join(', ')}`);
1166
1166
  if (!j.body1 || typeof j.body1.part !== 'string')
1167
1167
  fail('JOINT_SCHEMA', path, 'a joint carries a part (body1)');
1168
1168
  partOf(m, j.body1.part, path);
@@ -1247,7 +1247,7 @@ function checkJoint(asset, j) {
1247
1247
  fail('JOINT_SCHEMA', row, 'a row fills the part it is fastened to; the mounting plane has no length to fill');
1248
1248
  if (!['X', 'Y', 'Z'].includes(r.axis))
1249
1249
  fail('JOINT_SCHEMA', row, `a row runs along X, Y or Z, not ${String(r.axis)}`);
1250
- if (r.axis.toLowerCase() === V3_FACES[childFaceOf(asset.document.model, j)])
1250
+ if (r.axis.toLowerCase() === FIGURE_FACES[childFaceOf(asset.document.model, j)])
1251
1251
  fail('JOINT_SCHEMA', row, `a row runs across the face it is fastened to; ${r.axis} is out of that face`);
1252
1252
  if (typeof r.pitch === 'number' ? !(Number.isFinite(r.pitch) && r.pitch > 0) : !(r.pitch && typeof r.pitch === 'object' && r.pitch.from))
1253
1253
  fail('GEOMETRY_DOMAIN', row, 'a row has a positive pitch, or one that follows a length');
@@ -1282,7 +1282,7 @@ function writeJoint(asset, j) {
1282
1282
  const own = AXES.map(a => j.origin?.turn?.[a] ?? 0);
1283
1283
  /* The child as it stands: its quarter turn, then its twist about the normal of the face it is fastened by. */
1284
1284
  const childFace = childFaceOf(m, j);
1285
- const normal = V3_FACES[childFace];
1285
+ const normal = FIGURE_FACES[childFace];
1286
1286
  const standing = j.origin?.twist ? mul3(axisTurn(normal, j.origin.twist), turnMatrix(own)) : turnMatrix(own);
1287
1287
  /* In the parent's frame: its twist about the face's normal there, and the quarter turn that is left. */
1288
1288
  const inParent = mul3(back, standing);
@@ -1373,7 +1373,7 @@ function standingTurnOf(asset, part, path) {
1373
1373
  fail('EDIT_TARGET', path, `${part} is turned (${saidTurn(turn)}); fastening to a turned part by its faces is covered only for quarter turns`);
1374
1374
  const quarter = turn.every(t => t === 0) ? IDENTITY : turnMatrix(turn);
1375
1375
  /* A twisted part stands twisted about the normal of the face it is fastened by. */
1376
- return record?.origin?.twist ? mul3(axisTurn(V3_FACES[childFaceOf(asset.document.model, record)], record.origin.twist), quarter) : quarter;
1376
+ return record?.origin?.twist ? mul3(axisTurn(FIGURE_FACES[childFaceOf(asset.document.model, record)], record.origin.twist), quarter) : quarter;
1377
1377
  }
1378
1378
  /** How a part stands before its twist: its quarter turn only. */
1379
1379
  function standingQuarterOf(asset, part, path) {
@@ -1689,7 +1689,7 @@ function rewriteJoints(asset) {
1689
1689
  * was no longer there, and the author could not save (TARGET_ABSENT on joints.*, 2026-09-25).
1690
1690
  */
1691
1691
  /** The records follow a part's new name -- as a joint's parent, as its child, and as the joint a follower follows -- and the nodes are made again from them. */
1692
- export function renameV3PartInJoints(asset, from, to) {
1692
+ export function renameFigurePartInJoints(asset, from, to) {
1693
1693
  const joints = asset.joints;
1694
1694
  if (!joints?.length)
1695
1695
  return;
@@ -1746,7 +1746,7 @@ function refuseIfRead(asset, part, spaceFittedAgain = false) {
1746
1746
  * A copied part is fastened as the original is: its own record, the same faces and place, so it is not held by nodes
1747
1747
  * no record makes. A moving joint gets its own control -- a copy is a second thing, not the original's shadow.
1748
1748
  */
1749
- export function copyV3PartJoint(asset, from, to) {
1749
+ export function copyFigurePartJoint(asset, from, to) {
1750
1750
  const joints = (asset.joints ?? []);
1751
1751
  const j = joints.find(o => o.body1.part === from);
1752
1752
  if (!j)
@@ -1799,7 +1799,7 @@ function baseUnitOf(type) {
1799
1799
  * size or place becomes the number it is now. One thing is refused, by name: a row laid along it, or hanging in it --
1800
1800
  * a row is made from its record, and a record needs the part (ADR-0093).
1801
1801
  */
1802
- export function releaseV3PartJoints(asset, part, options = {}) {
1802
+ export function releaseFigurePartJoints(asset, part, options = {}) {
1803
1803
  const a = asset;
1804
1804
  const m = a.document.model;
1805
1805
  const notice = { released: [], fixed: [], controls: [] };
@@ -1815,7 +1815,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
1815
1815
  const inputs = (over = {}) => ({ ...a.designInputs, ...a.stateDefaults, ...over });
1816
1816
  const controls = m.inputs.filter((i) => i.role === 'state');
1817
1817
  const rest = Object.fromEntries(controls.map((i) => [i.id, Math.min(i.max, Math.max(i.min, 0))]));
1818
- const graph = compileV3Graph(m);
1818
+ const graph = compileFigureGraph(m);
1819
1819
  const atRest = graph.evaluate(inputs(rest)).values;
1820
1820
  const now = graph.evaluate(inputs()).values;
1821
1821
  /* A part moving with a joint that goes: its own control, over what the leader's distance or angle came to. */
@@ -1848,7 +1848,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
1848
1848
  return measure;
1849
1849
  const probe = structuredClone(m);
1850
1850
  const ref = stableDimension(probe, part, measure.from.axis, where);
1851
- const value = round6(Number(compileV3Graph(probe).evaluate(inputs()).values[ref]) * (measure.times ?? 1) + (measure.plus ?? 0));
1851
+ const value = round6(Number(compileFigureGraph(probe).evaluate(inputs()).values[ref]) * (measure.times ?? 1) + (measure.plus ?? 0));
1852
1852
  notice.fixed.push({ part: where, value, unit: 'mm' });
1853
1853
  return value;
1854
1854
  };
@@ -1930,7 +1930,36 @@ export function releaseV3PartJoints(asset, part, options = {}) {
1930
1930
  for (const ref of old)
1931
1931
  if (!pose.args.includes(ref))
1932
1932
  dropIfUnused(a, ref, ownInput(m, child, ref) ? ref : undefined);
1933
- notice.released.push({ part: child, ...(control && !m.inputs.some((i) => i.id === control) ? { control } : {}) });
1933
+ notice.released.push({ part: child, ...(control ? { control } : {}) });
1934
+ }
1935
+ /*
1936
+ A control goes with its joint once nothing reads it -- read only after every part is let go of: a lamp and the shade
1937
+ that followed it, both released, left the lamp's control behind while the shade still read it (2026-09-27).
1938
+ */
1939
+ const readAnywhere = (id) => m.nodes.some((n) => n.args.includes(id) || stringsIn(n.params).includes(id)) || stringsIn(a.occupancy).includes(id) || stringsIn(a.document.capabilities).includes(id);
1940
+ for (const r of notice.released) {
1941
+ if (!r.control)
1942
+ continue;
1943
+ if (readAnywhere(r.control)) {
1944
+ delete r.control;
1945
+ continue;
1946
+ }
1947
+ const id = r.control;
1948
+ m.inputs = m.inputs.filter((i) => i.id !== id);
1949
+ delete a.stateDefaults[id];
1950
+ delete a.designInputs[id];
1951
+ /* Its clip and its label go with it, as a motion's do (removeMotion). */
1952
+ if (a.drivers)
1953
+ a.drivers = a.drivers.filter((d) => d.state !== id);
1954
+ if (a.drivers && !a.drivers.length)
1955
+ delete a.drivers;
1956
+ if (a.stateInputs?.[id]) {
1957
+ const { [id]: _gone, ...rest } = a.stateInputs;
1958
+ if (Object.keys(rest).length)
1959
+ a.stateInputs = rest;
1960
+ else
1961
+ delete a.stateInputs;
1962
+ }
1934
1963
  }
1935
1964
  if (own)
1936
1965
  stripJoint(a, own);
@@ -2037,7 +2066,7 @@ function setJoint(asset, joint) {
2037
2066
  if (!jointAbove(asset, j.body1.part) && !j.origin?.turn) {
2038
2067
  const turn = turnOf(asset.document.model, asset, j.body1.part, j.body1.part);
2039
2068
  /* A part turned by any angle about the normal of the face it is fastened by keeps that angle as its twist. */
2040
- const n = AXES.indexOf(V3_FACES[childFaceOf(asset.document.model, j)]);
2069
+ const n = AXES.indexOf(FIGURE_FACES[childFaceOf(asset.document.model, j)]);
2041
2070
  const quarter = (t) => t !== null && Number.isFinite(t) && Math.abs(t / 90 - Math.round(t / 90)) < 1e-9;
2042
2071
  const twist = !quarter(turn[n]) && turn.every((t, i) => i === n || t === 0) ? turn[n] : undefined;
2043
2072
  if (twist !== undefined && !j.origin?.twist)
@@ -2063,7 +2092,7 @@ function removeJoint(asset, id) {
2063
2092
  fail('JOINT_SCHEMA', `joints.${id}`, `${followers.map(f => f.id).join(', ')} follow ${id}; remove them first`);
2064
2093
  const m = asset.document.model;
2065
2094
  const child = j.body1.part;
2066
- const evaluated = compileV3Asset(asset).evaluate();
2095
+ const evaluated = compileFigureAsset(asset).evaluate();
2067
2096
  const placed = nodeById(m, child);
2068
2097
  const at = evaluated.values?.[placed.args[1]];
2069
2098
  if (!at?.t)
@@ -2118,7 +2147,7 @@ function fastenAsJoint(asset, a) {
2118
2147
  * The records are the canon: made again from them, the joints' nodes must be the nodes the asset holds. An asset
2119
2148
  * whose nodes or records were edited apart is refused, not quietly rebuilt (ADR-0093, chief architect's condition).
2120
2149
  */
2121
- export function checkV3Joints(asset) {
2150
+ export function checkFigureJoints(asset) {
2122
2151
  const joints = asset.joints;
2123
2152
  if (!joints?.length)
2124
2153
  return;
@@ -2670,7 +2699,7 @@ function occupancyLins(asset, over) {
2670
2699
  const own = occupiedExtentsOf(m, id, id, turns);
2671
2700
  const c = turns ? IDENTITY : worldTurnOf(m, asset, id);
2672
2701
  if (!c)
2673
- throw new V3ContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
2702
+ throw new FigureContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
2674
2703
  return Object.fromEntries(AXES.map((axis, i) => [axis, own[AXES[c[i].findIndex(v => v !== 0)]]]));
2675
2704
  };
2676
2705
  /*
@@ -2720,7 +2749,7 @@ function occupancyLins(asset, over) {
2720
2749
  sizes = sizesOf(place.id, turns);
2721
2750
  }
2722
2751
  catch (e) {
2723
- skipped.push({ part: place.id, reason: e instanceof V3ContractError ? e.message : 'not a part whose size this command can read' });
2752
+ skipped.push({ part: place.id, reason: e instanceof FigureContractError ? e.message : 'not a part whose size this command can read' });
2724
2753
  continue;
2725
2754
  }
2726
2755
  /*
@@ -2789,7 +2818,7 @@ function occupancyLins(asset, over) {
2789
2818
  }
2790
2819
  /*
2791
2820
  Both ends of every control are candidates, and which one is the low end is not decided here: a travel of
2792
- −40 mm puts the low end at the control's maximum (V3 designer's counterexample 2026-09-23, where the
2821
+ −40 mm puts the low end at the control's maximum (designer's counterexample 2026-09-23, where the
2793
2822
  proposal read −50..10 for a part that needs −90..50). Both candidates go into the same min and the same
2794
2823
  max, so the answer holds whichever way the travel runs, and holds at every design size, because the
2795
2824
  choice is made when the graph is evaluated rather than when the proposal is written.
@@ -2861,7 +2890,7 @@ function occupancyLins(asset, over) {
2861
2890
  }
2862
2891
  const valueOfLin = (values, l) => l.terms.reduce((s, t) => s + t.k * (values[t.ref] ?? NaN), l.c);
2863
2892
  /** What the parts come to, and what a person is being asked to confirm. Reads the asset; changes nothing. */
2864
- export function proposeV3Occupancy(asset, options = {}) {
2893
+ export function proposeFigureOccupancy(asset, options = {}) {
2865
2894
  const over = options.over ?? 'range';
2866
2895
  if (over !== 'rest' && over !== 'range')
2867
2896
  fail('SCHEMA', 'over', `${String(over)} is neither rest nor range`);
@@ -2874,7 +2903,7 @@ export function proposeV3Occupancy(asset, options = {}) {
2874
2903
  }
2875
2904
  if (!parts.length)
2876
2905
  fail('OCCUPANCY_NO_PARTS', 'occupancy', `no part could be measured${skipped.length ? `: ${skipped.map(s => `${s.part} — ${s.reason}`).join('; ')}` : ''}`);
2877
- const values = compileV3Graph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
2906
+ const values = compileFigureGraph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
2878
2907
  const at = Object.fromEntries(AXES.map(axis => [axis, { min: round6(Math.min(...low[axis].map(l => valueOfLin(values, l)))), max: round6(Math.max(...high[axis].map(l => valueOfLin(values, l)))) }]));
2879
2908
  return { over, at, parts, skipped, basis: fingerprintOf(source) };
2880
2909
  }
@@ -2882,7 +2911,7 @@ function declareOccupancy(asset, a) {
2882
2911
  const m = asset.document.model;
2883
2912
  if (!a.proposal || (a.proposal.over !== 'rest' && a.proposal.over !== 'range'))
2884
2913
  fail('SCHEMA', 'occupancy', 'a proposal is required; propose first, then confirm');
2885
- if (!V3_PLACEMENTS.includes(a.placement))
2914
+ if (!FIGURE_PLACEMENTS.includes(a.placement))
2886
2915
  fail('SCHEMA', 'occupancy', `${String(a.placement)} is not a placement`);
2887
2916
  /*
2888
2917
  The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does
@@ -2956,7 +2985,7 @@ function setMountingFace(asset, a) {
2956
2985
  const m = asset.document.model;
2957
2986
  if (!asset.occupancy)
2958
2987
  fail('OCCUPANCY_UNDECLARED', 'occupancy', 'declare the room the figure takes up before naming the face it stands on');
2959
- if (!Object.hasOwn(V3_FACES, String(a.face)))
2988
+ if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
2960
2989
  fail('SCHEMA', a.part, `${String(a.face)} is not a face`);
2961
2990
  const { place } = partOf(m, a.part, a.part);
2962
2991
  const id = `${a.part}.mount.${a.face}`;
@@ -2999,7 +3028,7 @@ function addBinding(asset, a) {
2999
3028
  fail('DUPLICATE_BINDING', id, 'a place with this id exists; replace it explicitly');
3000
3029
  if (a.role !== 'seat' && a.role !== 'grip')
3001
3030
  fail('ROLE', id, 'a place is a seat or a grip');
3002
- if (!Object.hasOwn(V3_FACES, String(a.face)))
3031
+ if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
3003
3032
  fail('SCHEMA', id, `${String(a.face)} is not a face`);
3004
3033
  const place = nodeById(m, a.part);
3005
3034
  if (!place || place.op !== 'place@1')
@@ -3054,7 +3083,7 @@ function dropBindingRefs(asset, binding) {
3054
3083
  if (!declared.has(ref) && !m.nodes.some((n) => n.args.includes(ref)))
3055
3084
  m.constants = m.constants.filter((c) => c.id !== ref);
3056
3085
  }
3057
- export function v3BindingsOf(asset) {
3086
+ export function figureBindingsOf(asset) {
3058
3087
  const m = asset.document.model;
3059
3088
  const constant = (ref) => m.constants.find((c) => c.id === ref)?.value;
3060
3089
  return asset.document.capabilities.bindings.map((b) => {
@@ -3184,7 +3213,7 @@ function setRepeat(asset, a) {
3184
3213
  So the longest row the figure may be placed at is limit x pitch. A new pitch moves that bound with it; the pitch is refused only when the length the figure
3185
3214
  is authored at would already hold more copies than the cap.
3186
3215
  */
3187
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3216
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3188
3217
  const lengthRef = layout.args[0];
3189
3218
  const authored = values[lengthRef];
3190
3219
  const limit = (values[layout.args[6]] ?? m.constants.find((c) => c.id === layout.args[6])?.value);
@@ -3205,7 +3234,7 @@ function setRepeat(asset, a) {
3205
3234
  /* The longest length follows the pitch, within the default range (ten times the authored length). */
3206
3235
  const length = m.inputs.find((i) => i.id === lengthRef && i.role === 'design');
3207
3236
  if (length) {
3208
- const longest = Math.min(round6(limit * pitch), round6(authored * V3_INSTANCE_SIZE_DEFAULT.times));
3237
+ const longest = Math.min(round6(limit * pitch), round6(authored * FIGURE_INSTANCE_SIZE_DEFAULT.times));
3209
3238
  length.max = Math.max(longest, authored);
3210
3239
  if (length.min > length.max)
3211
3240
  length.min = Math.min(length.min, authored);
@@ -3266,14 +3295,14 @@ function scaleUniformly(asset) {
3266
3295
  return asset;
3267
3296
  }
3268
3297
  /** Reads the asset; changes nothing. Refused for a figure that does not grow by one ratio. */
3269
- export function proposeV3Proportion(asset) {
3298
+ export function proposeFigureProportion(asset) {
3270
3299
  return proportionPlan(asset);
3271
3300
  }
3272
3301
  function proportionPlan(asset) {
3273
3302
  const m = asset.document.model;
3274
3303
  if (asset.sizing !== 'uniform')
3275
3304
  fail('SIZING_UNDECLARED', 'sizing', 'the figure does not grow by one ratio; declare that first (scale-uniformly), then every length can follow it');
3276
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3305
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3277
3306
  const sizeNow = Object.fromEntries(AXES.map(axis => [axis, values[uniformSizeOf(m, axis, 'sizing')]]));
3278
3307
  const plan = { dimensions: [], lengths: [], skipped: [] };
3279
3308
  /* To nine places: the ratio's own mean is kept to six, and its noise is not the author's (0.1, not 0.0999999999787). */
@@ -3310,7 +3339,7 @@ function proportionPlan(asset) {
3310
3339
  plan.skipped.push({ what: `${part}.place`, reason: 'stands by no fastening; fasten it for its place to follow' });
3311
3340
  }
3312
3341
  for (const j of (asset.joints ?? [])) {
3313
- const faceAxis = V3_FACES[childFaceOf(m, j)];
3342
+ const faceAxis = FIGURE_FACES[childFaceOf(m, j)];
3314
3343
  const gap = j.origin?.gap;
3315
3344
  if (typeof gap === 'number' && gap !== 0)
3316
3345
  plan.lengths.push({ joint: j.id, at: 'gap', axis: faceAxis, times: share(gap, faceAxis) });
@@ -3372,7 +3401,7 @@ function scalePosition(asset, a) {
3372
3401
  fail('TARGET_ABSENT', path, `the instance has no ${size}; declare it before keeping a place in proportion to it`);
3373
3402
  const i = AXES.indexOf(a.axis);
3374
3403
  const was = pose.args[i];
3375
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3404
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3376
3405
  const value = values[was];
3377
3406
  const of = values[size];
3378
3407
  if (!Number.isFinite(value))
@@ -3401,7 +3430,7 @@ function fixPosition(asset, a) {
3401
3430
  const was = pose.args[i];
3402
3431
  if (ownInput(m, a.part, was))
3403
3432
  return asset;
3404
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3433
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3405
3434
  const value = round6(values[was]);
3406
3435
  if (!Number.isFinite(value))
3407
3436
  fail('TARGET_ABSENT', path, `${was} has no value to keep`);
@@ -3422,4 +3451,4 @@ function fixPosition(asset, a) {
3422
3451
  dropIfUnused(asset, was);
3423
3452
  return asset;
3424
3453
  }
3425
- //# sourceMappingURL=v3-authoring-actions.js.map
3454
+ //# sourceMappingURL=authoring-actions.js.map