@hatiolab/figure-model 0.1.36 → 0.1.37

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 (53) hide show
  1. package/dist/index.d.ts +4 -3
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +4 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/v3-asset-types.d.ts +89 -16
  6. package/dist/v3-asset-types.d.ts.map +1 -1
  7. package/dist/v3-asset.d.ts.map +1 -1
  8. package/dist/v3-asset.js +34 -25
  9. package/dist/v3-asset.js.map +1 -1
  10. package/dist/v3-authoring-actions.d.ts +97 -0
  11. package/dist/v3-authoring-actions.d.ts.map +1 -0
  12. package/dist/v3-authoring-actions.js +384 -0
  13. package/dist/v3-authoring-actions.js.map +1 -0
  14. package/dist/v3-capabilities.d.ts.map +1 -1
  15. package/dist/v3-capabilities.js +4 -77
  16. package/dist/v3-capabilities.js.map +1 -1
  17. package/dist/v3-driver.d.ts +24 -1
  18. package/dist/v3-driver.d.ts.map +1 -1
  19. package/dist/v3-driver.js +68 -2
  20. package/dist/v3-driver.js.map +1 -1
  21. package/dist/v3-from-v2.d.ts +25 -1
  22. package/dist/v3-from-v2.d.ts.map +1 -1
  23. package/dist/v3-from-v2.js +598 -21
  24. package/dist/v3-from-v2.js.map +1 -1
  25. package/dist/v3-gate.d.ts +59 -6
  26. package/dist/v3-gate.d.ts.map +1 -1
  27. package/dist/v3-gate.js +291 -45
  28. package/dist/v3-gate.js.map +1 -1
  29. package/dist/v3-graph-types.d.ts +9 -1
  30. package/dist/v3-graph-types.d.ts.map +1 -1
  31. package/dist/v3-graph.d.ts.map +1 -1
  32. package/dist/v3-graph.js +53 -1
  33. package/dist/v3-graph.js.map +1 -1
  34. package/dist/v3-kernel-version.d.ts +1 -1
  35. package/dist/v3-kernel-version.js +1 -1
  36. package/dist/v3-mesh-compare.d.ts +2 -5
  37. package/dist/v3-mesh-compare.d.ts.map +1 -1
  38. package/dist/v3-mesh-compare.js +2 -145
  39. package/dist/v3-mesh-compare.js.map +1 -1
  40. package/dist/v3-shape-sampling.d.ts +14 -0
  41. package/dist/v3-shape-sampling.d.ts.map +1 -0
  42. package/dist/v3-shape-sampling.js +120 -0
  43. package/dist/v3-shape-sampling.js.map +1 -0
  44. package/dist/v3-surface.d.ts +15 -0
  45. package/dist/v3-surface.d.ts.map +1 -0
  46. package/dist/v3-surface.js +104 -0
  47. package/dist/v3-surface.js.map +1 -0
  48. package/docs/prototypes/v3-asset.schema.json +0 -8
  49. package/docs/v3-asset-persistence.md +1 -1
  50. package/docs/v3-legacy-risk-audit-2026-09-23.md +39 -0
  51. package/docs/v3-operator-contracts.md +1 -0
  52. package/docs/v3-shape-dimension-contract.md +111 -0
  53. package/package.json +1 -1
@@ -144,3 +144,114 @@ range, and says how it knows:
144
144
  - Theme mapping and the colour cache (renderer work; ADR-0087 stage review ruling 4).
145
145
  - Shape deformation beyond dimensions: bending, twisting, free-form profiles that change with state. Those would be
146
146
  new providers with their own contracts.
147
+
148
+ ## 4. 표시 여부 (visibility) — 안 보이는 것과 작은 것은 다르다
149
+
150
+ **V3 설계자 결정 2026-09-22.** V2 는 부품을 숨길 때 크기를 0 으로 만든다. V3 는 그 방법을 쓰지
151
+ 않는다. 0 을 아주 작은 양수로 바꾸는 것도 쓰지 않는다 — 확대·그림자·포인터 선택에서 차이가
152
+ 생기고, 「안 보임」이 「아주 작음」이 되어 버린다.
153
+
154
+ ### 규칙
155
+
156
+ 1. **형상의 치수는 언제나 양수다.** 이 원칙은 안 바뀐다.
157
+ 2. **표시 여부는 치수와 분리된 상태 의존 속성이다.** `appearance.visibility` 가 graph 의 `flag`
158
+ 값 하나를 가리킨다. 없으면 항상 보인다.
159
+ 3. **`flag` 는 치수·pose·occupancy 로 흘러갈 수 없다.** 타입이 막는다. `at-least@1` 만 flag 를
160
+ 만들고, `appearance.visibility` 만 flag 를 받는다.
161
+ 4. **숨긴 부품도 id·부착 관계·동작 연결을 유지한다.** 평가에서 빠지지 않는다.
162
+ 5. **숨긴 부품은 그리기·그림자·포인터 선택에서 빠진다.**
163
+ 6. **선언한 occupancy 는 표시 여부로 줄거나 늘지 않는다.** 검사는 숨긴 부품도 그대로 잰다.
164
+
165
+ ### 평가 계약 — 숨겨진 상태에서 무엇을 계산하는가
166
+
167
+ **숨긴 부품은 보일 때와 똑같은 치수를 계산한다.** 숨김 전용 계산 경로가 없다. 3 번 규칙이
168
+ 이것을 보장한다 — flag 가 치수 식에 닿을 수 없으니, 치수는 표시 여부와 무관하게 같은 값이다.
169
+ 그래서 「숨겨진 상태의 잘못된 치수」라는 상태가 존재할 수 없다.
170
+
171
+ 읽는 쪽이 쓰는 두 가지 목록이 갈린다.
172
+
173
+ | 목록 | 무엇이 들어가는가 | 누가 쓰는가 |
174
+ | --- | --- | --- |
175
+ | `v3WorldBoxesOf` | 그리는 몸통만 (`visible === false` 제외) | renderer, V2 비교 |
176
+ | `v3OccupiedBoxesOf` | 놓인 몸통 전부 (숨긴 것 포함) | occupancy 검사 |
177
+
178
+ ### 변환기가 V2 의 scale 0 을 옮기는 방법
179
+
180
+ 채널이 **모든 변하는 축에서 0 으로 시작하고, 처음 0 이 아닌 key 부터 끝까지 정확히 1** 이면
181
+ (= 부품 제 크기) visibility 로 옮긴다. 부품은 제 치수를 계속 갖고, flag 가 그 key 의 `at` 에서
182
+ 켜진다. 그 외 — 숨기면서 크기도 바꾸는 채널 — 은 `SCALE_ZERO` 로 거절한다.
183
+
184
+ 이것은 **의도적 재저작이다**(`method: 're-authored'`, `RE_AUTHORED_VISIBILITY` note). 0 과
185
+ 문턱 사이에서 V2 는 자라나는 부품을 그리고 V3 는 안 그린다. DRY_ROOM 에서 실측했다: 문턱은
186
+ 파라미터 범위의 1 %, 그 밖 모든 상태에서 V2 와 같고 차이는 그 1 % 안에서만 난다.
187
+
188
+ ## When `proven` may be said (added 2026-09-23, after two counterexamples from the V3 designer)
189
+
190
+ `proven` means the gate evaluated the corners of the state box and nothing else. That is only a proof when every
191
+ coordinate the figure draws takes its extreme at a corner, and the property that guarantees it is **multilinearity**:
192
+ degree one in each state input, holding the others fixed. A multilinear value stays multilinear however it is
193
+ combined, which is why it can be built up through a whole figure.
194
+
195
+ Every operator in the kernel is classified by name in `V3_STATE_EXTREME_BASIS`, with no pattern and no default, and
196
+ a test compares that list against `V3_OPERATORS`: a new operator is unclassified, and unclassified is `unknown`.
197
+
198
+ `multilinear`, under the conditions the gate checks: `add@1`; `mul@1` when no two arguments depend on the same state
199
+ input; `div@1` by a state-free denominator; `rigid@1` when no rotation angle depends on state; `axis-slide@1` when the
200
+ axis is state-free; `compose@1`, `attach@1` and `map-point@1` when every pose reaching them has a state-free rotation
201
+ — `compose(a, b).t` is `a.r·b.t + a.t`, so a rotation that moves multiplies a translation that moves, and
202
+ `attach(a, o, b)` is two compositions with `inverse(b).t = −b.rᵀ·b.t`, which is the same shape of product;
203
+ `place@1`, `member@1`, `assembly@1`, `repeat@1`, `select-item@1`, `point@1`, `between@1`, `box@1`, `cylinder@1`, the
204
+ layout operators, which refuse state outright; `feature@1`, whose pose is a linear combination of the shape's
205
+ dimensions with an axis fixed by the feature's name rather than its size; the shape providers, whose extents are
206
+ linear in their arguments; and `at-least@1`, whose flag no coordinate can read.
207
+
208
+ `monotone` — may stand as a final coordinate, may not be combined with anything else moving with the same input:
209
+ `min@1`, `max@1`, `curve@1`, `geomean@1`, each refusing arguments that share a state input.
210
+
211
+ `unknown` — nothing is claimed, and a state-dependent argument sends the asset to sampling: `axis-turn@1`, `sin@1`,
212
+ `distance@1`, and `polygon-shape@1`, whose world box is the largest of its vertex coordinates, so two vertices
213
+ carrying one input fold it the way `min@1` folds.
214
+
215
+ A value from anything outside the multilinear list is **bent** in each state input it depends on. A bent value is at best monotone in that
216
+ input, so it may stand as a final coordinate on its own, and it may not be combined with anything else that moves with
217
+ the same input. `min@1`, `max@1` and `curve@1` are refused outright when two of their own arguments share an input.
218
+
219
+ The two counterexamples that set this, both of which the gate had called `proven` with no violation:
220
+
221
+ | expression | widest at | the gate had said |
222
+ | --- | --- | --- |
223
+ | `10 + 100·min(q, 1−q)`, q in [0, 1] | q = ½, 20 mm outside | proven, 0 violations |
224
+ | `10 + 100·(√q − q)`, q in [0.01, 1], √ as `geomean(q, 1)` | q = ¼, 7.5 mm outside | proven, 0 violations |
225
+
226
+ A new operator is bent until someone shows it preserves multilinearity and adds it to the list. Refusing costs
227
+ coverage, not correctness: the asset falls back to `sampled`, which states its step and is not called a proof.
228
+
229
+ ### Fitting the volume and touching the plane are two claims
230
+
231
+ `proven` is about one thing: every part stays inside the declared volume. Whether the figure touches the plane it
232
+ is mounted on is the *smallest* gap over its parts, and a smallest folds the way `min@1` folds. Two boxes at 20q
233
+ and 20(1−q) mm above the floor each touch it at one end of q and both float 10 mm in the middle, with every
234
+ coordinate linear in q (V3 designer's counterexample, 2026-09-23). The corners were extremal and the gate still
235
+ missed it.
236
+
237
+ Reading it as the lowest point of the figure was no better: a box hanging entirely below the plane passed, because
238
+ its lowest point was under zero, and a design that reaches under the plane on purpose could not pass at all. That is
239
+ a one-sided position test, not contact.
240
+
241
+ Two things then have to hold for that face to lie in the plane, and the first alone is not enough: a box stood on
242
+ its side, with its right-hand face declared as the mounting face, passed because that face's centre happened to sit
243
+ at Y = 0. A vertical face and a horizontal floor are not the same plane. So the check is the face's reference point
244
+ against the plane, within a distance tolerance, **and** its normal parallel to the plane's, within an angle
245
+ tolerance. Whether the normal points into the plane or away from it is a separate contract — which way up the figure
246
+ is mounted — and is not asked here.
247
+
248
+ The plane is Y = 0 in the asset frame, for a floor figure and a ceiling one alike, because the origin is where the
249
+ figure is fastened. It is not read off `occupancy.bounds.y.max`: that moved the thing the figure hangs from whenever
250
+ someone widened the volume it takes up.
251
+
252
+ So an asset that needs the contact **names the face that makes it** — a placement and a `feature@1` on the same
253
+ shape, read by `resolveV3Surface`, the same reader the capability bindings use. The check is that face against the
254
+ plane, both ways. There is no smallest over parts left to fold, and what remains to ask is whether the face moves: a
255
+ face whose placement does not follow state sits in the same place at every state, so one evaluation settles it, and
256
+ a face that moves sends the asset to sampling, because corner extremality says nothing about an equality holding in
257
+ between. An asset that names no mounting face is not asked to touch anything.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatiolab/figure-model",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Figure 저작 결과의 정본 형식과 검증 — 재질별로 병합되고 이름이 붙은 부품 그래프. 3D 로 저작한 형상 하나가 씬 컴포넌트로 서는 데 필요한 것을 담는다.",
5
5
  "keywords": [
6
6
  "figure",