@hatiolab/figure-model 0.1.92 → 0.1.94

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 (320) 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} +73 -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 +20 -0
  8. package/dist/asset.d.ts.map +1 -0
  9. package/dist/{v3-asset.js → asset.js} +49 -28
  10. package/dist/asset.js.map +1 -0
  11. package/dist/{v3-authoring-actions.d.ts → authoring-actions.d.ts} +77 -66
  12. package/dist/authoring-actions.d.ts.map +1 -0
  13. package/dist/{v3-authoring-actions.js → authoring-actions.js} +129 -104
  14. package/dist/authoring-actions.js.map +1 -0
  15. package/dist/{v3-authoring-session.d.ts → authoring-session.d.ts} +23 -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/clip-keeping.d.ts +30 -0
  24. package/dist/clip-keeping.d.ts.map +1 -0
  25. package/dist/clip-keeping.js +77 -0
  26. package/dist/clip-keeping.js.map +1 -0
  27. package/dist/command-preview.d.ts +125 -0
  28. package/dist/command-preview.d.ts.map +1 -0
  29. package/dist/command-preview.js +246 -0
  30. package/dist/command-preview.js.map +1 -0
  31. package/dist/contract.d.ts +7 -0
  32. package/dist/contract.d.ts.map +1 -0
  33. package/dist/contract.js +12 -0
  34. package/dist/contract.js.map +1 -0
  35. package/dist/{v3-cost.d.ts → cost.d.ts} +5 -5
  36. package/dist/cost.d.ts.map +1 -0
  37. package/dist/{v3-cost.js → cost.js} +11 -11
  38. package/dist/cost.js.map +1 -0
  39. package/dist/{v3-create.d.ts → create.d.ts} +8 -8
  40. package/dist/{v3-create.d.ts.map → create.d.ts.map} +1 -1
  41. package/dist/{v3-create.js → create.js} +26 -26
  42. package/dist/create.js.map +1 -0
  43. package/dist/document.d.ts +5 -0
  44. package/dist/document.d.ts.map +1 -0
  45. package/dist/document.js +31 -0
  46. package/dist/document.js.map +1 -0
  47. package/dist/{v3-driver.d.ts → driver.d.ts} +10 -10
  48. package/dist/driver.d.ts.map +1 -0
  49. package/dist/{v3-driver.js → driver.js} +10 -10
  50. package/dist/driver.js.map +1 -0
  51. package/dist/{v3-editor-workspace.d.ts → editor-workspace.d.ts} +99 -73
  52. package/dist/editor-workspace.d.ts.map +1 -0
  53. package/dist/{v3-editor-workspace.js → editor-workspace.js} +48 -32
  54. package/dist/editor-workspace.js.map +1 -0
  55. package/dist/{v3-examples.d.ts → examples.d.ts} +5 -5
  56. package/dist/examples.d.ts.map +1 -0
  57. package/dist/{v3-examples.js → examples.js} +11 -11
  58. package/dist/examples.js.map +1 -0
  59. package/dist/{v3-follow.d.ts → follow.d.ts} +5 -5
  60. package/dist/follow.d.ts.map +1 -0
  61. package/dist/{v3-follow.js → follow.js} +2 -2
  62. package/dist/follow.js.map +1 -0
  63. package/dist/{v3-gate.d.ts → gate.d.ts} +36 -28
  64. package/dist/gate.d.ts.map +1 -0
  65. package/dist/{v3-gate.js → gate.js} +54 -48
  66. package/dist/gate.js.map +1 -0
  67. package/dist/{v3-graph-types.d.ts → graph-types.d.ts} +33 -33
  68. package/dist/graph-types.d.ts.map +1 -0
  69. package/dist/graph-types.js +2 -0
  70. package/dist/graph-types.js.map +1 -0
  71. package/dist/{v3-graph.d.ts → graph.d.ts} +14 -14
  72. package/dist/graph.d.ts.map +1 -0
  73. package/dist/{v3-graph.js → graph.js} +16 -16
  74. package/dist/graph.js.map +1 -0
  75. package/dist/index.d.ts +39 -33
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +39 -33
  78. package/dist/index.js.map +1 -1
  79. package/dist/{v3-instance-size.d.ts → instance-size.d.ts} +14 -14
  80. package/dist/instance-size.d.ts.map +1 -0
  81. package/dist/{v3-instance-size.js → instance-size.js} +13 -13
  82. package/dist/instance-size.js.map +1 -0
  83. package/dist/json.d.ts +12 -0
  84. package/dist/json.d.ts.map +1 -0
  85. package/dist/{v3-json.js → json.js} +13 -13
  86. package/dist/json.js.map +1 -0
  87. package/dist/kernel-version.d.ts +8 -0
  88. package/dist/kernel-version.d.ts.map +1 -0
  89. package/dist/{v3-kernel-version.js → kernel-version.js} +4 -4
  90. package/dist/kernel-version.js.map +1 -0
  91. package/dist/{v3-part-edit.d.ts → part-edit.d.ts} +12 -11
  92. package/dist/part-edit.d.ts.map +1 -0
  93. package/dist/{v3-part-edit.js → part-edit.js} +84 -68
  94. package/dist/part-edit.js.map +1 -0
  95. package/dist/proposal.d.ts +55 -0
  96. package/dist/proposal.d.ts.map +1 -0
  97. package/dist/{v3-proposal.js → proposal.js} +29 -21
  98. package/dist/proposal.js.map +1 -0
  99. package/dist/{v3-public-inputs.d.ts → public-inputs.d.ts} +4 -4
  100. package/dist/public-inputs.d.ts.map +1 -0
  101. package/dist/{v3-public-inputs.js → public-inputs.js} +3 -3
  102. package/dist/public-inputs.js.map +1 -0
  103. package/dist/recipe-build.d.ts +27 -0
  104. package/dist/recipe-build.d.ts.map +1 -0
  105. package/dist/{v3-recipe-build.js → recipe-build.js} +9 -9
  106. package/dist/recipe-build.js.map +1 -0
  107. package/dist/recipe-from-parts.d.ts +8 -0
  108. package/dist/recipe-from-parts.d.ts.map +1 -0
  109. package/dist/{v3-recipe-from-parts.js → recipe-from-parts.js} +9 -9
  110. package/dist/recipe-from-parts.js.map +1 -0
  111. package/dist/{v3-recipe.d.ts → recipe.d.ts} +25 -25
  112. package/dist/recipe.d.ts.map +1 -0
  113. package/dist/{v3-recipe.js → recipe.js} +30 -30
  114. package/dist/recipe.js.map +1 -0
  115. package/dist/release-contract.d.ts +19 -0
  116. package/dist/release-contract.d.ts.map +1 -0
  117. package/dist/{v3-release-contract.js → release-contract.js} +8 -7
  118. package/dist/release-contract.js.map +1 -0
  119. package/dist/{v3-render-batches.d.ts → render-batches.d.ts} +8 -8
  120. package/dist/render-batches.d.ts.map +1 -0
  121. package/dist/{v3-render-batches.js → render-batches.js} +4 -4
  122. package/dist/render-batches.js.map +1 -0
  123. package/dist/resize-plan.d.ts +61 -0
  124. package/dist/resize-plan.d.ts.map +1 -0
  125. package/dist/resize-plan.js +189 -0
  126. package/dist/resize-plan.js.map +1 -0
  127. package/dist/room.d.ts +53 -0
  128. package/dist/room.d.ts.map +1 -0
  129. package/dist/room.js +90 -0
  130. package/dist/room.js.map +1 -0
  131. package/dist/{v3-score.d.ts → score.d.ts} +19 -19
  132. package/dist/score.d.ts.map +1 -0
  133. package/dist/{v3-score.js → score.js} +32 -32
  134. package/dist/score.js.map +1 -0
  135. package/dist/shape-provider.d.ts +37 -0
  136. package/dist/shape-provider.d.ts.map +1 -0
  137. package/dist/shape-provider.js +69 -0
  138. package/dist/shape-provider.js.map +1 -0
  139. package/dist/{v3-shape-sampling.d.ts → shape-sampling.d.ts} +4 -4
  140. package/dist/shape-sampling.d.ts.map +1 -0
  141. package/dist/{v3-shape-sampling.js → shape-sampling.js} +3 -3
  142. package/dist/shape-sampling.js.map +1 -0
  143. package/dist/size-edit.d.ts +38 -0
  144. package/dist/size-edit.d.ts.map +1 -0
  145. package/dist/size-edit.js +178 -0
  146. package/dist/size-edit.js.map +1 -0
  147. package/dist/{v3-surface.d.ts → surface.d.ts} +5 -5
  148. package/dist/surface.d.ts.map +1 -0
  149. package/dist/{v3-surface.js → surface.js} +7 -7
  150. package/dist/surface.js.map +1 -0
  151. package/dist/{v3-words.d.ts → words.d.ts} +3 -3
  152. package/dist/words.d.ts.map +1 -0
  153. package/dist/{v3-words.js → words.js} +3 -3
  154. package/dist/words.js.map +1 -0
  155. package/docs/{assembly-constraints-v3.md → assembly-constraints.md} +1 -1
  156. package/docs/{v3-asset-persistence.md → asset-persistence.md} +8 -8
  157. package/docs/{v3-authoring-transactions.md → authoring-transactions.md} +2 -2
  158. package/docs/{v3-capability-bindings.md → capability-bindings.md} +8 -8
  159. package/docs/{v3-common-kernel-experiment.md → common-kernel-experiment.md} +4 -4
  160. package/docs/{v3-core-semantics.md → core-semantics.md} +5 -5
  161. package/docs/{v3-cutover.md → cutover.md} +4 -4
  162. package/docs/{v3-design.md → design.md} +11 -11
  163. package/docs/{v3-document-contract.md → document-contract.md} +9 -9
  164. package/docs/{v3-dryroom-profile-validation.md → dryroom-profile-validation.md} +4 -4
  165. package/docs/{v3-editor-integration.md → editor-integration.md} +6 -6
  166. package/docs/{v3-full-conveyor-validation.md → full-conveyor-validation.md} +4 -4
  167. package/docs/{v3-full-dryroom-validation.md → full-dryroom-validation.md} +4 -4
  168. package/docs/{v3-full-forklift-validation.md → full-forklift-validation.md} +5 -5
  169. package/docs/{v3-full-robot-validation.md → full-robot-validation.md} +4 -4
  170. package/docs/{v3-layout-system.md → layout-system.md} +4 -4
  171. package/docs/{v3-legacy-risk-audit-2026-09-23.md → legacy-risk-audit-2026-09-23.md} +5 -5
  172. package/docs/{v3-minimal-model.md → minimal-model.md} +6 -6
  173. package/docs/{v3-motion-contract.md → motion-contract.md} +3 -3
  174. package/docs/{v3-openusd-review.md → openusd-review.md} +8 -8
  175. package/docs/{v3-operator-contracts.md → operator-contracts.md} +6 -6
  176. package/docs/{v3-payload-collision-validation.md → payload-collision-validation.md} +4 -4
  177. package/docs/{v3-payload-contact-validation.md → payload-contact-validation.md} +5 -5
  178. package/docs/prototypes/README.md +10 -10
  179. package/docs/prototypes/{v3-asset-samples.mjs → asset-samples.mjs} +6 -6
  180. package/docs/prototypes/{v3-asset-store.mjs → asset-store.mjs} +3 -3
  181. package/docs/prototypes/{v3-asset-store.test.mjs → asset-store.test.mjs} +6 -6
  182. package/docs/prototypes/asset.mjs +1 -0
  183. package/docs/prototypes/{v3-asset.schema.json → asset.schema.json} +2 -2
  184. package/docs/prototypes/{v3-asset.test.mjs → asset.test.mjs} +10 -10
  185. package/docs/prototypes/authoring-session.mjs +1 -0
  186. package/docs/prototypes/{v3-authoring-session.test.mjs → authoring-session.test.mjs} +6 -6
  187. package/docs/prototypes/capability-plan.mjs +1 -0
  188. package/docs/prototypes/{v3-capability-plan.test.mjs → capability-plan.test.mjs} +5 -5
  189. package/docs/prototypes/{v3-capability-samples.mjs → capability-samples.mjs} +2 -2
  190. package/docs/prototypes/document.mjs +2 -0
  191. package/docs/prototypes/{v3-document.schema.json → document.schema.json} +1 -1
  192. package/docs/prototypes/{v3-document.test.mjs → document.test.mjs} +7 -7
  193. package/docs/prototypes/{v3-dryroom-mesh.mjs → dryroom-mesh.mjs} +1 -1
  194. package/docs/prototypes/{v3-dryroom-mesh.test.mjs → dryroom-mesh.test.mjs} +1 -1
  195. package/docs/prototypes/{v3-editor-browser.mjs → editor-browser.mjs} +13 -13
  196. package/docs/prototypes/editor-workspace.mjs +1 -0
  197. package/docs/prototypes/{v3-editor-workspace.test.mjs → editor-workspace.test.mjs} +3 -3
  198. package/docs/prototypes/{v3-feature-pattern.test.mjs → feature-pattern.test.mjs} +1 -1
  199. package/docs/prototypes/{v3-full-conveyor.mjs → full-conveyor.mjs} +2 -2
  200. package/docs/prototypes/{v3-full-conveyor.test.mjs → full-conveyor.test.mjs} +3 -3
  201. package/docs/prototypes/{v3-full-dryroom.mjs → full-dryroom.mjs} +2 -2
  202. package/docs/prototypes/{v3-full-dryroom.test.mjs → full-dryroom.test.mjs} +3 -3
  203. package/docs/prototypes/{v3-full-forklift.mjs → full-forklift.mjs} +2 -2
  204. package/docs/prototypes/{v3-full-forklift.test.mjs → full-forklift.test.mjs} +3 -3
  205. package/docs/prototypes/{v3-full-robot.mjs → full-robot.mjs} +2 -2
  206. package/docs/prototypes/{v3-full-robot.test.mjs → full-robot.test.mjs} +4 -4
  207. package/docs/prototypes/{v3-kernel.test.mjs → kernel.test.mjs} +1 -1
  208. package/docs/prototypes/{v3-layout.test.mjs → layout.test.mjs} +1 -1
  209. package/docs/prototypes/{v3-more-samples.test.mjs → more-samples.test.mjs} +2 -2
  210. package/docs/prototypes/{v3-payload-collision.mjs → payload-collision.mjs} +2 -2
  211. package/docs/prototypes/{v3-payload-collision.test.mjs → payload-collision.test.mjs} +3 -3
  212. package/docs/prototypes/{v3-payload-fit.mjs → payload-fit.mjs} +2 -2
  213. package/docs/prototypes/{v3-payload-fit.test.mjs → payload-fit.test.mjs} +4 -4
  214. package/docs/prototypes/render-full-dryroom.mjs +12 -12
  215. package/docs/prototypes/{v3-repeated-binding.mjs → repeated-binding.mjs} +1 -1
  216. package/docs/prototypes/{v3-repeated-binding.test.mjs → repeated-binding.test.mjs} +6 -6
  217. package/docs/prototypes/{v3-runtime-browser.mjs → runtime-browser.mjs} +26 -26
  218. package/docs/prototypes/{v3-samples.test.mjs → samples.test.mjs} +1 -1
  219. package/docs/prototypes/{v3-spatial-plan.test.mjs → spatial-plan.test.mjs} +1 -1
  220. package/docs/prototypes/typed-plan.mjs +3 -0
  221. package/docs/prototypes/{v3-typed-plan.test.mjs → typed-plan.test.mjs} +1 -1
  222. package/docs/prototypes/{v3-typed-repeat.test.mjs → typed-repeat.test.mjs} +2 -2
  223. package/docs/{v3-repeated-binding-validation.md → repeated-binding-validation.md} +4 -4
  224. package/docs/{v3-research-review.md → research-review.md} +2 -2
  225. package/docs/{v3-review-2026-09-22.md → review-2026-09-22.md} +10 -10
  226. package/docs/{v3-runtime-extraction.md → runtime-extraction.md} +16 -16
  227. package/docs/{v3-sample-validation-expanded.md → sample-validation-expanded.md} +5 -5
  228. package/docs/{v3-sample-validation.md → sample-validation.md} +3 -3
  229. package/docs/{v3-shape-dimension-contract.md → shape-dimension-contract.md} +12 -12
  230. package/docs/{v3-shapeassembly-reproduction.md → shapeassembly-reproduction.md} +4 -4
  231. package/docs/{v3-spatial-contracts.md → spatial-contracts.md} +5 -5
  232. package/docs/status.md +12 -0
  233. package/docs/{v3-storage-integration.md → storage-integration.md} +5 -5
  234. package/docs/{v3-typed-repeat-contracts.md → typed-repeat-contracts.md} +3 -3
  235. package/package.json +3 -3
  236. package/dist/v3-asset-types.d.ts.map +0 -1
  237. package/dist/v3-asset-types.js +0 -2
  238. package/dist/v3-asset-types.js.map +0 -1
  239. package/dist/v3-asset.d.ts +0 -8
  240. package/dist/v3-asset.d.ts.map +0 -1
  241. package/dist/v3-asset.js.map +0 -1
  242. package/dist/v3-authoring-actions.d.ts.map +0 -1
  243. package/dist/v3-authoring-actions.js.map +0 -1
  244. package/dist/v3-authoring-session.d.ts.map +0 -1
  245. package/dist/v3-authoring-session.js.map +0 -1
  246. package/dist/v3-capabilities.d.ts +0 -3
  247. package/dist/v3-capabilities.d.ts.map +0 -1
  248. package/dist/v3-capabilities.js.map +0 -1
  249. package/dist/v3-contract.d.ts +0 -7
  250. package/dist/v3-contract.d.ts.map +0 -1
  251. package/dist/v3-contract.js +0 -12
  252. package/dist/v3-contract.js.map +0 -1
  253. package/dist/v3-cost.d.ts.map +0 -1
  254. package/dist/v3-cost.js.map +0 -1
  255. package/dist/v3-create.js.map +0 -1
  256. package/dist/v3-document.d.ts +0 -5
  257. package/dist/v3-document.d.ts.map +0 -1
  258. package/dist/v3-document.js +0 -31
  259. package/dist/v3-document.js.map +0 -1
  260. package/dist/v3-driver.d.ts.map +0 -1
  261. package/dist/v3-driver.js.map +0 -1
  262. package/dist/v3-editor-workspace.d.ts.map +0 -1
  263. package/dist/v3-editor-workspace.js.map +0 -1
  264. package/dist/v3-examples.d.ts.map +0 -1
  265. package/dist/v3-examples.js.map +0 -1
  266. package/dist/v3-follow.d.ts.map +0 -1
  267. package/dist/v3-follow.js.map +0 -1
  268. package/dist/v3-gate.d.ts.map +0 -1
  269. package/dist/v3-gate.js.map +0 -1
  270. package/dist/v3-graph-types.d.ts.map +0 -1
  271. package/dist/v3-graph-types.js +0 -2
  272. package/dist/v3-graph-types.js.map +0 -1
  273. package/dist/v3-graph.d.ts.map +0 -1
  274. package/dist/v3-graph.js.map +0 -1
  275. package/dist/v3-instance-size.d.ts.map +0 -1
  276. package/dist/v3-instance-size.js.map +0 -1
  277. package/dist/v3-json.d.ts +0 -12
  278. package/dist/v3-json.d.ts.map +0 -1
  279. package/dist/v3-json.js.map +0 -1
  280. package/dist/v3-kernel-version.d.ts +0 -8
  281. package/dist/v3-kernel-version.d.ts.map +0 -1
  282. package/dist/v3-kernel-version.js.map +0 -1
  283. package/dist/v3-part-edit.d.ts.map +0 -1
  284. package/dist/v3-part-edit.js.map +0 -1
  285. package/dist/v3-proposal.d.ts +0 -42
  286. package/dist/v3-proposal.d.ts.map +0 -1
  287. package/dist/v3-proposal.js.map +0 -1
  288. package/dist/v3-public-inputs.d.ts.map +0 -1
  289. package/dist/v3-public-inputs.js.map +0 -1
  290. package/dist/v3-recipe-build.d.ts +0 -27
  291. package/dist/v3-recipe-build.d.ts.map +0 -1
  292. package/dist/v3-recipe-build.js.map +0 -1
  293. package/dist/v3-recipe-from-parts.d.ts +0 -8
  294. package/dist/v3-recipe-from-parts.d.ts.map +0 -1
  295. package/dist/v3-recipe-from-parts.js.map +0 -1
  296. package/dist/v3-recipe.d.ts.map +0 -1
  297. package/dist/v3-recipe.js.map +0 -1
  298. package/dist/v3-release-contract.d.ts +0 -19
  299. package/dist/v3-release-contract.d.ts.map +0 -1
  300. package/dist/v3-release-contract.js.map +0 -1
  301. package/dist/v3-render-batches.d.ts.map +0 -1
  302. package/dist/v3-render-batches.js.map +0 -1
  303. package/dist/v3-score.d.ts.map +0 -1
  304. package/dist/v3-score.js.map +0 -1
  305. package/dist/v3-shape-sampling.d.ts.map +0 -1
  306. package/dist/v3-shape-sampling.js.map +0 -1
  307. package/dist/v3-surface.d.ts.map +0 -1
  308. package/dist/v3-surface.js.map +0 -1
  309. package/dist/v3-words.d.ts.map +0 -1
  310. package/dist/v3-words.js.map +0 -1
  311. package/docs/prototypes/v3-asset.mjs +0 -1
  312. package/docs/prototypes/v3-authoring-session.mjs +0 -1
  313. package/docs/prototypes/v3-capability-plan.mjs +0 -1
  314. package/docs/prototypes/v3-document.mjs +0 -2
  315. package/docs/prototypes/v3-editor-workspace.mjs +0 -1
  316. package/docs/prototypes/v3-typed-plan.mjs +0 -3
  317. package/docs/v3-status.md +0 -50
  318. /package/docs/prototypes/{v3-kernel.mjs → kernel.mjs} +0 -0
  319. /package/docs/prototypes/{v3-linear-comparison.py → linear-comparison.py} +0 -0
  320. /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,27 @@
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 { settleFigureClips } from "./clip-keeping.js";
25
+ import { compileFigureAsset, figurePlacementOf, upgradeFigurePlacement } from "./asset.js";
26
+ import { checkPublicInput, keepPublicInputs } from "./public-inputs.js";
27
+ import { figurePartsExtentOf } from "./gate.js";
28
+ import { compileFigureGraph, figureShapeFaces } from "./graph.js";
29
+ import { figureFollowableAxesOf } from "./shape-provider.js";
30
+ import { FigureContractError } from "./contract.js";
31
+ import { AXES, REPEAT_LIMIT } from "./words.js";
32
+ import { defaultInstanceSizeRange, foldInstanceSizeRange, figureInstanceSizeLimitsOf, FIGURE_INSTANCE_SIZE_DEFAULT, FIGURE_INSTANCE_SIZE_INPUTS } from "./instance-size.js";
33
+ import { FIGURE_PLACEMENTS } from "./asset-types.js";
32
34
  const fail = (code, path, message) => {
33
- throw new V3ContractError(code, path, message);
35
+ throw new FigureContractError(code, path, message);
34
36
  };
35
37
  /** 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' };
38
+ export const FIGURE_FACES = { left: 'x', right: 'x', bottom: 'y', top: 'y', back: 'z', front: 'z' };
37
39
  /**
38
40
  * UsdPhysics's joint types (ADR-0093, amended 2026-09-25: the joint follows USD). A `fixed` joint is a fastening; a
39
41
  * `revolute` joint turns -- with no limits it turns without end, its value a phase (-180..180), as USD's revolute
40
42
  * joint without limits does; a `prismatic` joint slides.
41
43
  */
42
- export const V3_JOINT_TYPES = ['fixed', 'revolute', 'prismatic'];
44
+ export const FIGURE_JOINT_TYPES = ['fixed', 'revolute', 'prismatic'];
43
45
  const SIGN = { left: -1, right: 1, bottom: -1, top: 1, back: -1, front: 1 };
44
46
  const OPPOSITE = { left: 'right', right: 'left', bottom: 'top', top: 'bottom', back: 'front', front: 'back' };
45
47
  const clone = (asset) => structuredClone(asset);
@@ -172,7 +174,7 @@ const OUTER = { x: ['left', 'right'], y: ['bottom', 'top'], z: ['back', 'front']
172
174
  /** A part's declared faces, from its shape's own definition, or a refusal saying the shape has none. */
173
175
  function facesOfPart(m, id, path) {
174
176
  const { shape } = partOf(m, id, path);
175
- const faces = v3ShapeFaces(shape.op);
177
+ const faces = figureShapeFaces(shape.op);
176
178
  if (!faces)
177
179
  fail('EDIT_TARGET', path, `${id} is a ${shape.op}; it has no flat face to fasten by`);
178
180
  return { shape, faces: faces };
@@ -189,12 +191,12 @@ function childFaceOf(m, j) {
189
191
  fail('JOINT_SCHEMA', path, 'a part stands on the mounting plane by a face of its own, not by its centre');
190
192
  /* 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
193
  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]);
194
+ if (Object.hasOwn(FIGURE_FACES, named))
195
+ return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === FIGURE_FACES[named] && SIGN[f] === -SIGN[named]);
194
196
  const theirs = facesOfPart(m, j.body0.part, path).faces[j.body0.face];
195
197
  if (!theirs)
196
198
  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);
199
+ return Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === theirs.axis && SIGN[f] === -theirs.normal);
198
200
  }
199
201
  const planeOf = (shape, face) => face.plane.map(p => ({ ref: shape.args[p.arg], k: p.k }));
200
202
  /** The part's outer faces on each axis in its own frame; an axis without a pair of flat faces has none. */
@@ -328,7 +330,7 @@ function sourceRef(m, asset, source, path) {
328
330
  *
329
331
  * "B is as wide as A" has to keep meaning that when A is told to follow something else. Reading A's shape
330
332
  * 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
333
+ * left B on the old value (designer's counterexample 2026-09-23: A went to 300 and B stayed at 100). Every
332
334
  * dimension a command touches gets a node of its own, `<part>.dimension.<axis>`, and that is what the shape
333
335
  * reads and what anything following it reads. Relinking rewrites what that node computes, and everyone pointing
334
336
  * at it comes along — fastenings included, since they measure the same references.
@@ -356,7 +358,7 @@ function stableDimension(m, part, axis, path) {
356
358
  const dimensionSource = (m, part, axis) => nodeById(m, `${part}.dimension.${axis}`)?.args?.[0];
357
359
  /** Whether this side of the part already follows something, rather than reading its own control. */
358
360
  /**
359
- * A part's own number for a dimension: a design input named under the part. `addV3Box` names it `<part>.size.<axis>`,
361
+ * A part's own number for a dimension: a design input named under the part. `addFigureBox` names it `<part>.size.<axis>`,
360
362
  * the editor's shape palette `<part>.dimension.width` (and height, depth); both are the part's own, and a dimension
361
363
  * read from one follows nothing. Reading only the first name made a palette box look linked as soon as any command
362
364
  * had routed its dimension, and a link was refused as a replacement (2026-09-24, :3300).
@@ -415,9 +417,17 @@ function unlinkDimension(asset, a) {
415
417
  if (!followsAlready(m, a.part, a.axis))
416
418
  fail('EDIT_TARGET', path, `${a.part}'s ${a.axis} does not follow anything`);
417
419
  const id = `${a.part}.size.${a.axis}`;
418
- if (m.inputs.some((i) => i.id === id))
420
+ /*
421
+ The part's own field may still be there: the declared room was measured from it and still reads it, so it stayed
422
+ when the measure began to follow (ADR-0097). Setting the measure back to fixed takes that field back -- it is the
423
+ part's own number -- rather than refusing (found 2026-09-27: 「고정으로 되돌리기」 failed on any figure with a room).
424
+ Only a field of the part's own kind: anything else by that name is refused as before.
425
+ */
426
+ const kept = m.inputs.find((i) => i.id === id);
427
+ if (kept && !(kept.role === 'design' && kept.unit === 'mm'))
419
428
  fail('DUPLICATE_WRITER', path, `${id} already exists`);
420
- m.inputs.push({ id, unit: 'mm', min: Number.MIN_VALUE, max: Number.MAX_VALUE, role: 'design' });
429
+ if (!kept)
430
+ m.inputs.push({ id, unit: 'mm', min: Number.MIN_VALUE, max: Number.MAX_VALUE, role: 'design' });
421
431
  asset.designInputs[id] = a.mm;
422
432
  const node = nodeById(m, `${a.part}.dimension.${a.axis}`);
423
433
  const was = node.args[0];
@@ -476,7 +486,7 @@ function attach(asset, a, twist) {
476
486
  if (a.part === to.part)
477
487
  fail('ATTACH_CYCLE', path, 'a part cannot be fastened to itself');
478
488
  const byCentre = a.face === 'centre';
479
- if (!byCentre && !Object.hasOwn(V3_FACES, String(a.face)))
489
+ if (!byCentre && !Object.hasOwn(FIGURE_FACES, String(a.face)))
480
490
  fail('SCHEMA', path, `${String(a.face)} is not a face`);
481
491
  /* The target's faces are the ones its shape declares: a portal's opening as well as its outer faces. */
482
492
  const target = facesOfPart(m, to.part, path);
@@ -492,10 +502,10 @@ function attach(asset, a, twist) {
492
502
  fail('ATTACH_FACE', path, `${a.part} fastened by its centre sits on ${to.face}; flush has no meaning for a centre`);
493
503
  /* By its centre, the part is measured as the face that would meet the target's; only its offset differs. */
494
504
  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));
505
+ a = { ...a, face: Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === -sign) };
506
+ if (FIGURE_FACES[a.face] !== axis)
507
+ 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`);
508
+ const wanted = Object.keys(FIGURE_FACES).find(f => FIGURE_FACES[f] === axis && SIGN[f] === (facing === 'meet' ? -sign : sign));
499
509
  if (facing === 'meet' && a.face !== wanted)
500
510
  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
511
  if (facing === 'flush' && a.face !== wanted)
@@ -640,13 +650,13 @@ function standOnPlane(asset, a, path) {
640
650
  if (a.facing !== undefined && a.facing !== 'meet')
641
651
  fail('SCHEMA', path, 'a part standing on the plane meets it; flush says nothing here');
642
652
  /* Which plane it is, the figure's placement says: the floor or the ceiling is Y = 0, a wall is Z = 0 (ADR-0093). */
643
- const placement = (asset.occupancy?.placement ?? 'floor');
653
+ const placement = figurePlacementOf(asset);
644
654
  const takes = planeFacesOf(placement);
645
655
  const normal = placement === 'wall' ? 'z' : 'y';
646
656
  if (!takes.includes(a.face))
647
657
  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`);
658
+ ? `${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`
659
+ : `${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
660
  if (parentOf(m, a.part) && !a.replace)
651
661
  fail('ATTACH_REPLACED', path, `${a.part} is already fastened to ${parentOf(m, a.part)}; pass replace to stand it on the plane instead`);
652
662
  const mine = standingSpansOf(m, asset, a.part, path);
@@ -701,7 +711,7 @@ function detach(asset, a) {
701
711
  /*
702
712
  Detaching keeps the part where it stands, and keeps it moving the way it moved. Taking the world pose and
703
713
  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
714
+ applied a second time, so a part with 20 mm of travel jumped to 40 (designer's counterexample
705
715
  2026-09-23).
706
716
 
707
717
  What the part's pose has to gain is exactly what the parent's chain was contributing — and that is a plain
@@ -710,7 +720,7 @@ function detach(asset, a) {
710
720
  */
711
721
  for (let up = parent; up; up = parentOf(m, up))
712
722
  requireUnturned(m, asset, up, path);
713
- const evaluated = compileV3Asset(asset).evaluate();
723
+ const evaluated = compileFigureAsset(asset).evaluate();
714
724
  const above = evaluated.values?.[nodeById(m, `${parent}.world`)?.outputs?.pose ?? nodeById(m, `${parent}.chain`)?.outputs?.pose ?? nodeById(m, `${parent}.pose`).outputs.pose];
715
725
  if (!above || !above.t)
716
726
  fail('TARGET_ABSENT', path, `${parent} has no pose to read`);
@@ -833,8 +843,17 @@ function reseat(asset, id, to, place) {
833
843
  dropIfUnused(asset, ref, ownInput(m, id, ref) ? ref : undefined);
834
844
  }
835
845
  /** Apply one authoring command. Throws on any refusal, leaving the asset it was given untouched. */
836
- export function applyV3Authoring(source, action) {
837
- compileV3Asset(source);
846
+ export function applyFigureAuthoring(source, action, consent) {
847
+ /*
848
+ Writing the joints again strips each moving joint's clip with it; which clips stay, go (only when agreed) or refuse
849
+ the command is settled here, against the figure as it was (clip-keeping.ts).
850
+ */
851
+ const before = upgradeFigurePlacement(source);
852
+ return settleFigureClips(before, authoring(before, action), consent);
853
+ }
854
+ function authoring(source, action) {
855
+ source = upgradeFigurePlacement(source);
856
+ compileFigureAsset(source);
838
857
  if (!action || typeof action !== 'object')
839
858
  fail('SCHEMA', 'action', 'an authoring action is required');
840
859
  /* Made of the commands it stands for, each checked as it would be alone. */
@@ -851,6 +870,9 @@ export function applyV3Authoring(source, action) {
851
870
  case 'attach':
852
871
  fastenAsJoint(asset, action);
853
872
  break;
873
+ case 'set-placement':
874
+ setPlacement(asset, action);
875
+ break;
854
876
  case 'detach': {
855
877
  const joint = jointAbove(asset, action.part);
856
878
  if (!joint)
@@ -989,28 +1011,19 @@ export function applyV3Authoring(source, action) {
989
1011
  rewriteJoints(asset);
990
1012
  /* An input a command took away takes its place on the board with it (ADR-0096). */
991
1013
  keepPublicInputs(asset);
992
- compileV3Asset(asset);
1014
+ compileFigureAsset(asset);
993
1015
  if ((action.kind === 'link-dimension' || action.kind === 'make-repeat' || action.kind === 'set-joint') && action.narrowRange)
994
1016
  narrowDeclaredSize(asset);
995
1017
  holdsOverDeclaredSize(asset, source);
996
1018
  return asset;
997
1019
  }
998
1020
  /* ----------------------------------------------------------- instance size */
999
- /** The shapes whose first measures lie along X, Y and Z, and which axes they have. */
1000
- const MEASURED_ALONG = {
1001
- 'box-shape@1': ['x', 'y', 'z'],
1002
- 'rounded-box@1': ['x', 'y', 'z'],
1003
- 'portal-profile@2': ['x', 'y', 'z'],
1004
- 'hollow-box@2': ['x', 'y', 'z'],
1005
- /* A cylinder's radius is read across (X), its length along (Y). */
1006
- 'cylinder-shape@1': ['x', 'y']
1007
- };
1008
1021
  function followSize(source, a) {
1009
1022
  const path = `${a.part}.follow-size`;
1010
1023
  const shape = source.document.model.nodes.find((n) => n.id === `${a.part}.shape`);
1011
1024
  if (!shape)
1012
1025
  fail('TARGET_ABSENT', path, `${a.part} is not a part`);
1013
- const along = MEASURED_ALONG[shape.op] ?? [];
1026
+ const along = figureFollowableAxesOf(shape.op);
1014
1027
  if (!along.length)
1015
1028
  fail('EDIT_TARGET', path, `${a.part} is a ${shape.op}; its measures do not lie along X, Y and Z`);
1016
1029
  const axes = a.axes ?? along;
@@ -1020,28 +1033,28 @@ function followSize(source, a) {
1020
1033
  let asset = source;
1021
1034
  const m = asset.document.model;
1022
1035
  if (!AXES.every(axis => sizeInputOf(m, axis))) {
1023
- const at = v3PartsExtentOf(asset);
1036
+ const at = figurePartsExtentOf(asset);
1024
1037
  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)) });
1038
+ const range = foldInstanceSizeRange(start, figureInstanceSizeLimitsOf(asset, start)).range;
1039
+ asset = applyFigureAuthoring(asset, { kind: 'declare-instance-size', range, at, replace: AXES.some(axis => sizeInputOf(m, axis)) });
1027
1040
  }
1028
- const values = compileV3Graph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
1041
+ const values = compileFigureGraph(asset.document.model).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
1029
1042
  const argsNow = (asset.document.model.nodes.find((n) => n.id === `${a.part}.shape`)).args;
1030
1043
  for (const axis of axes) {
1031
1044
  const measure = values[argsNow[AXES.indexOf(axis)]];
1032
- const size = asset.designInputs[V3_INSTANCE_SIZE_INPUTS[axis]];
1045
+ const size = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
1033
1046
  if (!(typeof measure === 'number' && measure > 0 && typeof size === 'number' && size > 0))
1034
1047
  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 });
1048
+ asset = applyFigureAuthoring(asset, { kind: 'link-dimension', part: a.part, axis, source: { of: 'instance', axis }, times: measure / size, replace: true, narrowRange: true });
1036
1049
  }
1037
1050
  return asset;
1038
1051
  }
1039
- const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id === V3_INSTANCE_SIZE_INPUTS[axis]);
1052
+ const sizeInputOf = (m, axis) => m.inputs.find((i) => i.id === FIGURE_INSTANCE_SIZE_INPUTS[axis]);
1040
1053
  function declareInstanceSize(asset, a) {
1041
1054
  const m = asset.document.model;
1042
1055
  const declared = AXES.filter(axis => sizeInputOf(m, axis));
1043
1056
  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`);
1057
+ 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
1058
  if (!a.range || typeof a.range !== 'object')
1046
1059
  fail('SCHEMA', 'size', 'a range per axis is required');
1047
1060
  let measured;
@@ -1049,18 +1062,18 @@ function declareInstanceSize(asset, a) {
1049
1062
  const given = a.at?.[axis];
1050
1063
  if (given !== undefined)
1051
1064
  return given;
1052
- const existing = asset.designInputs[V3_INSTANCE_SIZE_INPUTS[axis]];
1065
+ const existing = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[axis]];
1053
1066
  if (typeof existing === 'number')
1054
1067
  return existing;
1055
1068
  /* The parts as they rest, not the room their joints carry them through (a door's swing). */
1056
1069
  if (!measured)
1057
- measured = v3PartsExtentOf(asset);
1070
+ measured = figurePartsExtentOf(asset);
1058
1071
  return measured[axis];
1059
1072
  };
1060
1073
  for (const axis of AXES) {
1061
1074
  const r = a.range[axis];
1062
1075
  const at = authoredOf(axis);
1063
- const path = V3_INSTANCE_SIZE_INPUTS[axis];
1076
+ const path = FIGURE_INSTANCE_SIZE_INPUTS[axis];
1064
1077
  if (!r || ![r.min, r.max, at].every(Number.isFinite))
1065
1078
  fail('SCHEMA', path, 'finite min, max and size required');
1066
1079
  if (!(r.min > 0))
@@ -1088,7 +1101,7 @@ function sizeRangeGaps(asset) {
1088
1101
  if (!AXES.every(axis => sizeInputOf(m, axis)))
1089
1102
  return [];
1090
1103
  const range = Object.fromEntries(AXES.map(axis => [axis, { min: sizeInputOf(m, axis).min, max: sizeInputOf(m, axis).max }]));
1091
- const limits = v3InstanceSizeLimitsOf(asset, range);
1104
+ const limits = figureInstanceSizeLimitsOf(asset, range);
1092
1105
  const { range: holds } = foldInstanceSizeRange(range, limits);
1093
1106
  const gaps = [];
1094
1107
  for (const axis of AXES) {
@@ -1108,9 +1121,9 @@ function narrowDeclaredSize(asset) {
1108
1121
  return;
1109
1122
  for (const gap of sizeRangeGaps(asset)) {
1110
1123
  const input = sizeInputOf(m, gap.axis);
1111
- const at = asset.designInputs[V3_INSTANCE_SIZE_INPUTS[gap.axis]];
1124
+ const at = asset.designInputs[FIGURE_INSTANCE_SIZE_INPUTS[gap.axis]];
1112
1125
  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`);
1126
+ 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
1127
  if (gap.bound === 'min')
1115
1128
  input.min = gap.need;
1116
1129
  else
@@ -1133,7 +1146,7 @@ function holdsOverDeclaredSize(asset, before) {
1133
1146
  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
1147
  if (!worse)
1135
1148
  continue;
1136
- const path = V3_INSTANCE_SIZE_INPUTS[gap.axis];
1149
+ const path = FIGURE_INSTANCE_SIZE_INPUTS[gap.axis];
1137
1150
  const by = gap.who ? ` (${gap.who})` : '';
1138
1151
  if (gap.bound === 'min')
1139
1152
  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 +1154,7 @@ function holdsOverDeclaredSize(asset, before) {
1141
1154
  }
1142
1155
  }
1143
1156
  /** What each part is fastened to, for a reader that wants the structure rather than the graph. */
1144
- export function v3AttachmentsOf(asset) {
1157
+ export function figureAttachmentsOf(asset) {
1145
1158
  const m = asset.document.model;
1146
1159
  const out = {};
1147
1160
  for (const n of m.nodes)
@@ -1161,8 +1174,8 @@ function checkJoint(asset, j) {
1161
1174
  const path = `joints.${String(j?.id)}`;
1162
1175
  if (!j || typeof j.id !== 'string' || !j.id.trim())
1163
1176
  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(', ')}`);
1177
+ if (!FIGURE_JOINT_TYPES.includes(j.type))
1178
+ fail('JOINT_SCHEMA', path, `${String(j.type)} is not a joint type; ${FIGURE_JOINT_TYPES.join(', ')}`);
1166
1179
  if (!j.body1 || typeof j.body1.part !== 'string')
1167
1180
  fail('JOINT_SCHEMA', path, 'a joint carries a part (body1)');
1168
1181
  partOf(m, j.body1.part, path);
@@ -1247,7 +1260,7 @@ function checkJoint(asset, j) {
1247
1260
  fail('JOINT_SCHEMA', row, 'a row fills the part it is fastened to; the mounting plane has no length to fill');
1248
1261
  if (!['X', 'Y', 'Z'].includes(r.axis))
1249
1262
  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)])
1263
+ if (r.axis.toLowerCase() === FIGURE_FACES[childFaceOf(asset.document.model, j)])
1251
1264
  fail('JOINT_SCHEMA', row, `a row runs across the face it is fastened to; ${r.axis} is out of that face`);
1252
1265
  if (typeof r.pitch === 'number' ? !(Number.isFinite(r.pitch) && r.pitch > 0) : !(r.pitch && typeof r.pitch === 'object' && r.pitch.from))
1253
1266
  fail('GEOMETRY_DOMAIN', row, 'a row has a positive pitch, or one that follows a length');
@@ -1282,7 +1295,7 @@ function writeJoint(asset, j) {
1282
1295
  const own = AXES.map(a => j.origin?.turn?.[a] ?? 0);
1283
1296
  /* The child as it stands: its quarter turn, then its twist about the normal of the face it is fastened by. */
1284
1297
  const childFace = childFaceOf(m, j);
1285
- const normal = V3_FACES[childFace];
1298
+ const normal = FIGURE_FACES[childFace];
1286
1299
  const standing = j.origin?.twist ? mul3(axisTurn(normal, j.origin.twist), turnMatrix(own)) : turnMatrix(own);
1287
1300
  /* In the parent's frame: its twist about the face's normal there, and the quarter turn that is left. */
1288
1301
  const inParent = mul3(back, standing);
@@ -1373,7 +1386,7 @@ function standingTurnOf(asset, part, path) {
1373
1386
  fail('EDIT_TARGET', path, `${part} is turned (${saidTurn(turn)}); fastening to a turned part by its faces is covered only for quarter turns`);
1374
1387
  const quarter = turn.every(t => t === 0) ? IDENTITY : turnMatrix(turn);
1375
1388
  /* 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;
1389
+ return record?.origin?.twist ? mul3(axisTurn(FIGURE_FACES[childFaceOf(asset.document.model, record)], record.origin.twist), quarter) : quarter;
1377
1390
  }
1378
1391
  /** How a part stands before its twist: its quarter turn only. */
1379
1392
  function standingQuarterOf(asset, part, path) {
@@ -1689,7 +1702,7 @@ function rewriteJoints(asset) {
1689
1702
  * was no longer there, and the author could not save (TARGET_ABSENT on joints.*, 2026-09-25).
1690
1703
  */
1691
1704
  /** 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) {
1705
+ export function renameFigurePartInJoints(asset, from, to) {
1693
1706
  const joints = asset.joints;
1694
1707
  if (!joints?.length)
1695
1708
  return;
@@ -1746,7 +1759,7 @@ function refuseIfRead(asset, part, spaceFittedAgain = false) {
1746
1759
  * 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
1760
  * no record makes. A moving joint gets its own control -- a copy is a second thing, not the original's shadow.
1748
1761
  */
1749
- export function copyV3PartJoint(asset, from, to) {
1762
+ export function copyFigurePartJoint(asset, from, to) {
1750
1763
  const joints = (asset.joints ?? []);
1751
1764
  const j = joints.find(o => o.body1.part === from);
1752
1765
  if (!j)
@@ -1799,7 +1812,7 @@ function baseUnitOf(type) {
1799
1812
  * 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
1813
  * a row is made from its record, and a record needs the part (ADR-0093).
1801
1814
  */
1802
- export function releaseV3PartJoints(asset, part, options = {}) {
1815
+ export function releaseFigurePartJoints(asset, part, options = {}) {
1803
1816
  const a = asset;
1804
1817
  const m = a.document.model;
1805
1818
  const notice = { released: [], fixed: [], controls: [] };
@@ -1815,7 +1828,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
1815
1828
  const inputs = (over = {}) => ({ ...a.designInputs, ...a.stateDefaults, ...over });
1816
1829
  const controls = m.inputs.filter((i) => i.role === 'state');
1817
1830
  const rest = Object.fromEntries(controls.map((i) => [i.id, Math.min(i.max, Math.max(i.min, 0))]));
1818
- const graph = compileV3Graph(m);
1831
+ const graph = compileFigureGraph(m);
1819
1832
  const atRest = graph.evaluate(inputs(rest)).values;
1820
1833
  const now = graph.evaluate(inputs()).values;
1821
1834
  /* A part moving with a joint that goes: its own control, over what the leader's distance or angle came to. */
@@ -1848,7 +1861,7 @@ export function releaseV3PartJoints(asset, part, options = {}) {
1848
1861
  return measure;
1849
1862
  const probe = structuredClone(m);
1850
1863
  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));
1864
+ const value = round6(Number(compileFigureGraph(probe).evaluate(inputs()).values[ref]) * (measure.times ?? 1) + (measure.plus ?? 0));
1852
1865
  notice.fixed.push({ part: where, value, unit: 'mm' });
1853
1866
  return value;
1854
1867
  };
@@ -2066,7 +2079,7 @@ function setJoint(asset, joint) {
2066
2079
  if (!jointAbove(asset, j.body1.part) && !j.origin?.turn) {
2067
2080
  const turn = turnOf(asset.document.model, asset, j.body1.part, j.body1.part);
2068
2081
  /* A part turned by any angle about the normal of the face it is fastened by keeps that angle as its twist. */
2069
- const n = AXES.indexOf(V3_FACES[childFaceOf(asset.document.model, j)]);
2082
+ const n = AXES.indexOf(FIGURE_FACES[childFaceOf(asset.document.model, j)]);
2070
2083
  const quarter = (t) => t !== null && Number.isFinite(t) && Math.abs(t / 90 - Math.round(t / 90)) < 1e-9;
2071
2084
  const twist = !quarter(turn[n]) && turn.every((t, i) => i === n || t === 0) ? turn[n] : undefined;
2072
2085
  if (twist !== undefined && !j.origin?.twist)
@@ -2092,7 +2105,7 @@ function removeJoint(asset, id) {
2092
2105
  fail('JOINT_SCHEMA', `joints.${id}`, `${followers.map(f => f.id).join(', ')} follow ${id}; remove them first`);
2093
2106
  const m = asset.document.model;
2094
2107
  const child = j.body1.part;
2095
- const evaluated = compileV3Asset(asset).evaluate();
2108
+ const evaluated = compileFigureAsset(asset).evaluate();
2096
2109
  const placed = nodeById(m, child);
2097
2110
  const at = evaluated.values?.[placed.args[1]];
2098
2111
  if (!at?.t)
@@ -2147,7 +2160,7 @@ function fastenAsJoint(asset, a) {
2147
2160
  * The records are the canon: made again from them, the joints' nodes must be the nodes the asset holds. An asset
2148
2161
  * whose nodes or records were edited apart is refused, not quietly rebuilt (ADR-0093, chief architect's condition).
2149
2162
  */
2150
- export function checkV3Joints(asset) {
2163
+ export function checkFigureJoints(asset) {
2151
2164
  const joints = asset.joints;
2152
2165
  if (!joints?.length)
2153
2166
  return;
@@ -2306,7 +2319,8 @@ function removeMotion(asset, a) {
2306
2319
  }
2307
2320
  /** A figure as one value, for saying "this is the figure I read". The declaration itself is left out of it. */
2308
2321
  function fingerprintOf(asset) {
2309
- const text = JSON.stringify({ ...asset, occupancy: asset.occupancy ? { ...asset.occupancy, bounds: null } : undefined });
2322
+ /* The room and the placement are left out: neither changes what the room would measure (ADR-0097). */
2323
+ const text = JSON.stringify({ ...asset, placement: undefined, occupancy: asset.occupancy ? { ...asset.occupancy, bounds: null } : undefined });
2310
2324
  let h1 = 0x811c9dc5;
2311
2325
  let h2 = 0x01000193;
2312
2326
  for (let i = 0; i < text.length; i++) {
@@ -2699,7 +2713,7 @@ function occupancyLins(asset, over) {
2699
2713
  const own = occupiedExtentsOf(m, id, id, turns);
2700
2714
  const c = turns ? IDENTITY : worldTurnOf(m, asset, id);
2701
2715
  if (!c)
2702
- throw new V3ContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
2716
+ throw new FigureContractError('EDIT_TARGET', id, `${id} is turned by other than quarter turns`);
2703
2717
  return Object.fromEntries(AXES.map((axis, i) => [axis, own[AXES[c[i].findIndex(v => v !== 0)]]]));
2704
2718
  };
2705
2719
  /*
@@ -2749,7 +2763,7 @@ function occupancyLins(asset, over) {
2749
2763
  sizes = sizesOf(place.id, turns);
2750
2764
  }
2751
2765
  catch (e) {
2752
- skipped.push({ part: place.id, reason: e instanceof V3ContractError ? e.message : 'not a part whose size this command can read' });
2766
+ skipped.push({ part: place.id, reason: e instanceof FigureContractError ? e.message : 'not a part whose size this command can read' });
2753
2767
  continue;
2754
2768
  }
2755
2769
  /*
@@ -2818,7 +2832,7 @@ function occupancyLins(asset, over) {
2818
2832
  }
2819
2833
  /*
2820
2834
  Both ends of every control are candidates, and which one is the low end is not decided here: a travel of
2821
- −40 mm puts the low end at the control's maximum (V3 designer's counterexample 2026-09-23, where the
2835
+ −40 mm puts the low end at the control's maximum (designer's counterexample 2026-09-23, where the
2822
2836
  proposal read −50..10 for a part that needs −90..50). Both candidates go into the same min and the same
2823
2837
  max, so the answer holds whichever way the travel runs, and holds at every design size, because the
2824
2838
  choice is made when the graph is evaluated rather than when the proposal is written.
@@ -2890,7 +2904,8 @@ function occupancyLins(asset, over) {
2890
2904
  }
2891
2905
  const valueOfLin = (values, l) => l.terms.reduce((s, t) => s + t.k * (values[t.ref] ?? NaN), l.c);
2892
2906
  /** What the parts come to, and what a person is being asked to confirm. Reads the asset; changes nothing. */
2893
- export function proposeV3Occupancy(asset, options = {}) {
2907
+ export function proposeFigureOccupancy(asset, options = {}) {
2908
+ asset = upgradeFigurePlacement(asset);
2894
2909
  const over = options.over ?? 'range';
2895
2910
  if (over !== 'rest' && over !== 'range')
2896
2911
  fail('SCHEMA', 'over', `${String(over)} is neither rest nor range`);
@@ -2903,28 +2918,35 @@ export function proposeV3Occupancy(asset, options = {}) {
2903
2918
  }
2904
2919
  if (!parts.length)
2905
2920
  fail('OCCUPANCY_NO_PARTS', 'occupancy', `no part could be measured${skipped.length ? `: ${skipped.map(s => `${s.part} — ${s.reason}`).join('; ')}` : ''}`);
2906
- const values = compileV3Graph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
2921
+ const values = compileFigureGraph(source.document.model).evaluate({ ...source.designInputs, ...source.stateDefaults }).values;
2907
2922
  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)))) }]));
2908
2923
  return { over, at, parts, skipped, basis: fingerprintOf(source) };
2909
2924
  }
2925
+ /**
2926
+ * The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does not
2927
+ * take -- a bottom on the floor, when the figure is to hang on a wall -- is named, not seated again without a word
2928
+ * (chief architect's condition, 2026-09-25): fasten it again by the face the new plane takes.
2929
+ */
2930
+ function setPlacement(asset, a) {
2931
+ if (!FIGURE_PLACEMENTS.includes(a.placement))
2932
+ fail('SCHEMA', 'placement', `${String(a.placement)} is not a placement`);
2933
+ const takes = planeFacesOf(a.placement);
2934
+ const misfit = (asset.joints ?? []).filter(j => 'plane' in j.body0 && !takes.includes(j.body1.face));
2935
+ if (misfit.length)
2936
+ fail('PLACEMENT_PLANE', 'placement', `${misfit.map(j => `${j.body1.part} (${j.body1.face})`).join(', ')} ${misfit.length > 1 ? 'are' : 'is'} fastened to the mounting plane by a face a ${a.placement} does not take (${takes.join(' or ')}); detach ${misfit.length > 1 ? 'them' : 'it'} or fasten ${misfit.length > 1 ? 'them' : 'it'} by ${takes.join(' or ')} first`);
2937
+ asset.placement = a.placement;
2938
+ return asset;
2939
+ }
2910
2940
  function declareOccupancy(asset, a) {
2911
2941
  const m = asset.document.model;
2912
2942
  if (!a.proposal || (a.proposal.over !== 'rest' && a.proposal.over !== 'range'))
2913
2943
  fail('SCHEMA', 'occupancy', 'a proposal is required; propose first, then confirm');
2914
- if (!V3_PLACEMENTS.includes(a.placement))
2915
- fail('SCHEMA', 'occupancy', `${String(a.placement)} is not a placement`);
2916
- /*
2917
- The placement says which plane the mounting plane is. A part already fastened to it by a face the new plane does
2918
- not take -- a bottom on the floor, when the figure is to hang on a wall -- is named, not seated again without a
2919
- word (chief architect's condition, 2026-09-25): fasten it again by the face the new plane takes.
2920
- */
2921
- const takes = planeFacesOf(a.placement);
2922
- const misfit = (asset.joints ?? []).filter(j => 'plane' in j.body0 && !takes.includes(j.body1.face));
2923
- if (misfit.length)
2924
- fail('PLACEMENT_PLANE', 'occupancy.placement', `${misfit.map(j => `${j.body1.part} (${j.body1.face})`).join(', ')} ${misfit.length > 1 ? 'are' : 'is'} fastened to the mounting plane by a face a ${a.placement} does not take (${takes.join(' or ')}); detach ${misfit.length > 1 ? 'them' : 'it'} or fasten ${misfit.length > 1 ? 'them' : 'it'} by ${takes.join(' or ')} first`);
2925
2944
  const now = fingerprintOf(asset);
2926
2945
  if (a.proposal.basis !== now)
2927
2946
  fail('OCCUPANCY_STALE', 'occupancy', 'the figure changed since this was proposed, so the volume it would declare is not the one that was looked at; propose again and confirm that');
2947
+ /* Declared with a placement, the placement is set by the same command as on its own: one value, one check. */
2948
+ if (a.placement !== undefined)
2949
+ setPlacement(asset, { kind: 'set-placement', placement: a.placement });
2928
2950
  const padding = {};
2929
2951
  for (const axis of AXES) {
2930
2952
  const pad = a.padding?.[axis] ?? 0;
@@ -2963,7 +2985,7 @@ function declareOccupancy(asset, a) {
2963
2985
  m.nodes.push({ id, op: 'affine-bounds@1', args, params: { layout: `L${layout.join('.')}` }, outputs: Object.fromEntries(['xmin', 'xmax', 'ymin', 'ymax', 'zmin', 'zmax'].map(p => [p, port(p)])) });
2964
2986
  const bounds = Object.fromEntries(AXES.map(axis => [axis, { min: port(`${axis}min`), max: port(`${axis}max`) }]));
2965
2987
  const was = stringsIn(asset.occupancy?.bounds);
2966
- asset.occupancy = { ...(asset.occupancy?.contact ? { contact: asset.occupancy.contact } : {}), bounds, placement: a.placement };
2988
+ asset.occupancy = { ...(asset.occupancy?.contact ? { contact: asset.occupancy.contact } : {}), bounds };
2967
2989
  /*
2968
2990
  The room declared before goes with what fed only it. Left in the graph, it kept reading where parts had been
2969
2991
  fastened, and the next fastening was refused over a room no one read (the gantry, 2026-09-25).
@@ -2972,11 +2994,13 @@ function declareOccupancy(asset, a) {
2972
2994
  dropIfUnused(asset, ref);
2973
2995
  /*
2974
2996
  A part's own measure the old room alone still read -- the part follows something else now -- goes too: kept, it
2975
- was a dial that changed nothing (the turntable after every part followed the one ratio, 2026-09-25).
2997
+ was a dial that changed nothing (the turntable after every part followed the one ratio, 2026-09-25). So does its
2998
+ own position or turn, once a fastening places it: a floor part's height position stayed as a field that moved
2999
+ nothing (browser check, ADR-0097, 2026-09-27). The room's refit names them before it is confirmed (figureRoomRefitOf).
2976
3000
  */
2977
3001
  const placed = new Set(m.nodes.filter((n) => n.op === 'place@1' || n.op === 'member@1').map((n) => n.id));
2978
3002
  for (const input of [...m.inputs])
2979
- if (input.role === 'design' && placed.has(String(input.id).split('.')[0]) && /\.(dimension|size)\.[a-z]+$/.test(input.id))
3003
+ if (input.role === 'design' && placed.has(String(input.id).split('.')[0]) && /\.(dimension|size|position|rotation)\.[a-z]+$/.test(input.id))
2980
3004
  dropIfUnused(asset, input.id, input.id);
2981
3005
  return asset;
2982
3006
  }
@@ -2985,7 +3009,7 @@ function setMountingFace(asset, a) {
2985
3009
  const m = asset.document.model;
2986
3010
  if (!asset.occupancy)
2987
3011
  fail('OCCUPANCY_UNDECLARED', 'occupancy', 'declare the room the figure takes up before naming the face it stands on');
2988
- if (!Object.hasOwn(V3_FACES, String(a.face)))
3012
+ if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
2989
3013
  fail('SCHEMA', a.part, `${String(a.face)} is not a face`);
2990
3014
  const { place } = partOf(m, a.part, a.part);
2991
3015
  const id = `${a.part}.mount.${a.face}`;
@@ -3028,7 +3052,7 @@ function addBinding(asset, a) {
3028
3052
  fail('DUPLICATE_BINDING', id, 'a place with this id exists; replace it explicitly');
3029
3053
  if (a.role !== 'seat' && a.role !== 'grip')
3030
3054
  fail('ROLE', id, 'a place is a seat or a grip');
3031
- if (!Object.hasOwn(V3_FACES, String(a.face)))
3055
+ if (!Object.hasOwn(FIGURE_FACES, String(a.face)))
3032
3056
  fail('SCHEMA', id, `${String(a.face)} is not a face`);
3033
3057
  const place = nodeById(m, a.part);
3034
3058
  if (!place || place.op !== 'place@1')
@@ -3083,7 +3107,7 @@ function dropBindingRefs(asset, binding) {
3083
3107
  if (!declared.has(ref) && !m.nodes.some((n) => n.args.includes(ref)))
3084
3108
  m.constants = m.constants.filter((c) => c.id !== ref);
3085
3109
  }
3086
- export function v3BindingsOf(asset) {
3110
+ export function figureBindingsOf(asset) {
3087
3111
  const m = asset.document.model;
3088
3112
  const constant = (ref) => m.constants.find((c) => c.id === ref)?.value;
3089
3113
  return asset.document.capabilities.bindings.map((b) => {
@@ -3213,7 +3237,7 @@ function setRepeat(asset, a) {
3213
3237
  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
3214
3238
  is authored at would already hold more copies than the cap.
3215
3239
  */
3216
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3240
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3217
3241
  const lengthRef = layout.args[0];
3218
3242
  const authored = values[lengthRef];
3219
3243
  const limit = (values[layout.args[6]] ?? m.constants.find((c) => c.id === layout.args[6])?.value);
@@ -3234,7 +3258,7 @@ function setRepeat(asset, a) {
3234
3258
  /* The longest length follows the pitch, within the default range (ten times the authored length). */
3235
3259
  const length = m.inputs.find((i) => i.id === lengthRef && i.role === 'design');
3236
3260
  if (length) {
3237
- const longest = Math.min(round6(limit * pitch), round6(authored * V3_INSTANCE_SIZE_DEFAULT.times));
3261
+ const longest = Math.min(round6(limit * pitch), round6(authored * FIGURE_INSTANCE_SIZE_DEFAULT.times));
3238
3262
  length.max = Math.max(longest, authored);
3239
3263
  if (length.min > length.max)
3240
3264
  length.min = Math.min(length.min, authored);
@@ -3295,14 +3319,15 @@ function scaleUniformly(asset) {
3295
3319
  return asset;
3296
3320
  }
3297
3321
  /** Reads the asset; changes nothing. Refused for a figure that does not grow by one ratio. */
3298
- export function proposeV3Proportion(asset) {
3322
+ export function proposeFigureProportion(asset) {
3323
+ asset = upgradeFigurePlacement(asset);
3299
3324
  return proportionPlan(asset);
3300
3325
  }
3301
3326
  function proportionPlan(asset) {
3302
3327
  const m = asset.document.model;
3303
3328
  if (asset.sizing !== 'uniform')
3304
3329
  fail('SIZING_UNDECLARED', 'sizing', 'the figure does not grow by one ratio; declare that first (scale-uniformly), then every length can follow it');
3305
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3330
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3306
3331
  const sizeNow = Object.fromEntries(AXES.map(axis => [axis, values[uniformSizeOf(m, axis, 'sizing')]]));
3307
3332
  const plan = { dimensions: [], lengths: [], skipped: [] };
3308
3333
  /* 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). */
@@ -3339,7 +3364,7 @@ function proportionPlan(asset) {
3339
3364
  plan.skipped.push({ what: `${part}.place`, reason: 'stands by no fastening; fasten it for its place to follow' });
3340
3365
  }
3341
3366
  for (const j of (asset.joints ?? [])) {
3342
- const faceAxis = V3_FACES[childFaceOf(m, j)];
3367
+ const faceAxis = FIGURE_FACES[childFaceOf(m, j)];
3343
3368
  const gap = j.origin?.gap;
3344
3369
  if (typeof gap === 'number' && gap !== 0)
3345
3370
  plan.lengths.push({ joint: j.id, at: 'gap', axis: faceAxis, times: share(gap, faceAxis) });
@@ -3401,7 +3426,7 @@ function scalePosition(asset, a) {
3401
3426
  fail('TARGET_ABSENT', path, `the instance has no ${size}; declare it before keeping a place in proportion to it`);
3402
3427
  const i = AXES.indexOf(a.axis);
3403
3428
  const was = pose.args[i];
3404
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3429
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3405
3430
  const value = values[was];
3406
3431
  const of = values[size];
3407
3432
  if (!Number.isFinite(value))
@@ -3430,7 +3455,7 @@ function fixPosition(asset, a) {
3430
3455
  const was = pose.args[i];
3431
3456
  if (ownInput(m, a.part, was))
3432
3457
  return asset;
3433
- const values = compileV3Graph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3458
+ const values = compileFigureGraph(m).evaluate({ ...asset.designInputs, ...asset.stateDefaults }).values;
3434
3459
  const value = round6(values[was]);
3435
3460
  if (!Number.isFinite(value))
3436
3461
  fail('TARGET_ABSENT', path, `${was} has no value to keep`);
@@ -3451,4 +3476,4 @@ function fixPosition(asset, a) {
3451
3476
  dropIfUnused(asset, was);
3452
3477
  return asset;
3453
3478
  }
3454
- //# sourceMappingURL=v3-authoring-actions.js.map
3479
+ //# sourceMappingURL=authoring-actions.js.map