@hatiolab/figure-model 0.1.76 → 0.1.78

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 (135) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +60 -138
  3. package/dist/index.d.ts +1 -13
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +3 -26
  6. package/dist/index.js.map +1 -1
  7. package/dist/v3-asset-types.d.ts +1 -2
  8. package/dist/v3-asset-types.d.ts.map +1 -1
  9. package/dist/v3-asset.js +1 -1
  10. package/dist/v3-asset.js.map +1 -1
  11. package/dist/v3-authoring-actions.d.ts +9 -12
  12. package/dist/v3-authoring-actions.d.ts.map +1 -1
  13. package/dist/v3-authoring-actions.js +4 -5
  14. package/dist/v3-authoring-actions.js.map +1 -1
  15. package/dist/v3-capabilities.js +4 -1
  16. package/dist/v3-capabilities.js.map +1 -1
  17. package/dist/v3-cost.d.ts.map +1 -1
  18. package/dist/v3-cost.js +3 -5
  19. package/dist/v3-cost.js.map +1 -1
  20. package/dist/v3-create.d.ts +1 -1
  21. package/dist/v3-create.d.ts.map +1 -1
  22. package/dist/v3-examples.js +1 -1
  23. package/dist/v3-gate.d.ts +7 -8
  24. package/dist/v3-gate.d.ts.map +1 -1
  25. package/dist/v3-gate.js +11 -14
  26. package/dist/v3-gate.js.map +1 -1
  27. package/dist/v3-graph.d.ts +1 -2
  28. package/dist/v3-graph.d.ts.map +1 -1
  29. package/dist/v3-graph.js +5 -7
  30. package/dist/v3-graph.js.map +1 -1
  31. package/dist/v3-instance-size.d.ts +3 -5
  32. package/dist/v3-instance-size.d.ts.map +1 -1
  33. package/dist/v3-instance-size.js +2 -4
  34. package/dist/v3-instance-size.js.map +1 -1
  35. package/dist/v3-kernel-version.d.ts +1 -1
  36. package/dist/v3-kernel-version.js +1 -1
  37. package/dist/v3-part-edit.d.ts +4 -5
  38. package/dist/v3-part-edit.d.ts.map +1 -1
  39. package/dist/v3-part-edit.js +2 -2
  40. package/dist/v3-part-edit.js.map +1 -1
  41. package/dist/v3-proposal.js +1 -1
  42. package/dist/v3-proposal.js.map +1 -1
  43. package/dist/v3-release-contract.js +1 -1
  44. package/dist/v3-release-contract.js.map +1 -1
  45. package/dist/v3-score.d.ts +4 -5
  46. package/dist/v3-score.d.ts.map +1 -1
  47. package/dist/v3-score.js +7 -8
  48. package/dist/v3-score.js.map +1 -1
  49. package/dist/v3-words.d.ts +105 -0
  50. package/dist/v3-words.d.ts.map +1 -0
  51. package/dist/v3-words.js +107 -0
  52. package/dist/v3-words.js.map +1 -0
  53. package/docs/assembly-constraints-v3.md +13 -23
  54. package/docs/v3-asset-persistence.md +4 -4
  55. package/docs/v3-common-kernel-experiment.md +1 -1
  56. package/docs/v3-core-semantics.md +3 -3
  57. package/docs/v3-cutover.md +5 -3
  58. package/docs/v3-design.md +13 -26
  59. package/docs/v3-editor-integration.md +2 -2
  60. package/docs/v3-full-conveyor-validation.md +3 -3
  61. package/docs/v3-layout-system.md +1 -1
  62. package/docs/v3-minimal-model.md +0 -1
  63. package/docs/v3-motion-contract.md +21 -56
  64. package/docs/v3-runtime-extraction.md +7 -7
  65. package/docs/v3-sample-validation-expanded.md +1 -1
  66. package/docs/v3-sample-validation.md +1 -1
  67. package/docs/v3-shape-dimension-contract.md +23 -57
  68. package/docs/v3-status.md +5 -5
  69. package/docs/v3-storage-integration.md +4 -4
  70. package/package.json +2 -20
  71. package/dist/blueprint-shape.d.ts +0 -10
  72. package/dist/blueprint-shape.d.ts.map +0 -1
  73. package/dist/blueprint-shape.js +0 -194
  74. package/dist/blueprint-shape.js.map +0 -1
  75. package/dist/blueprint.d.ts +0 -73
  76. package/dist/blueprint.d.ts.map +0 -1
  77. package/dist/blueprint.js +0 -388
  78. package/dist/blueprint.js.map +0 -1
  79. package/dist/cost-chart.d.ts +0 -35
  80. package/dist/cost-chart.d.ts.map +0 -1
  81. package/dist/cost-chart.js +0 -150
  82. package/dist/cost-chart.js.map +0 -1
  83. package/dist/cost.d.ts +0 -162
  84. package/dist/cost.d.ts.map +0 -1
  85. package/dist/cost.js +0 -194
  86. package/dist/cost.js.map +0 -1
  87. package/dist/gate.d.ts +0 -65
  88. package/dist/gate.d.ts.map +0 -1
  89. package/dist/gate.js +0 -284
  90. package/dist/gate.js.map +0 -1
  91. package/dist/grouping.d.ts +0 -121
  92. package/dist/grouping.d.ts.map +0 -1
  93. package/dist/grouping.js +0 -224
  94. package/dist/grouping.js.map +0 -1
  95. package/dist/keys.d.ts +0 -45
  96. package/dist/keys.d.ts.map +0 -1
  97. package/dist/keys.js +0 -84
  98. package/dist/keys.js.map +0 -1
  99. package/dist/origin.d.ts +0 -33
  100. package/dist/origin.d.ts.map +0 -1
  101. package/dist/origin.js +0 -46
  102. package/dist/origin.js.map +0 -1
  103. package/dist/release-contract.d.ts +0 -38
  104. package/dist/release-contract.d.ts.map +0 -1
  105. package/dist/release-contract.js +0 -246
  106. package/dist/release-contract.js.map +0 -1
  107. package/dist/sizing.d.ts +0 -231
  108. package/dist/sizing.d.ts.map +0 -1
  109. package/dist/sizing.js +0 -550
  110. package/dist/sizing.js.map +0 -1
  111. package/dist/types.d.ts +0 -1233
  112. package/dist/types.d.ts.map +0 -1
  113. package/dist/types.js +0 -547
  114. package/dist/types.js.map +0 -1
  115. package/dist/v3-from-v2.d.ts +0 -203
  116. package/dist/v3-from-v2.d.ts.map +0 -1
  117. package/dist/v3-from-v2.js +0 -2053
  118. package/dist/v3-from-v2.js.map +0 -1
  119. package/dist/v3-mesh-compare.d.ts +0 -24
  120. package/dist/v3-mesh-compare.d.ts.map +0 -1
  121. package/dist/v3-mesh-compare.js +0 -55
  122. package/dist/v3-mesh-compare.js.map +0 -1
  123. package/dist/validate.d.ts +0 -7
  124. package/dist/validate.d.ts.map +0 -1
  125. package/dist/validate.js +0 -1119
  126. package/dist/validate.js.map +0 -1
  127. package/dist/visual-evidence.d.ts +0 -21
  128. package/dist/visual-evidence.d.ts.map +0 -1
  129. package/dist/visual-evidence.js +0 -52
  130. package/dist/visual-evidence.js.map +0 -1
  131. package/docs/design.md +0 -193
  132. package/docs/format-survey.md +0 -108
  133. package/docs/format.md +0 -704
  134. package/docs/sizing.md +0 -183
  135. package/docs/validation.md +0 -357
package/docs/v3-design.md CHANGED
@@ -23,25 +23,14 @@ v3의 핵심은 **치수에 따라 달라지는 3차원 배치 함수**다. 문
23
23
  쓴다. 임의 CAD, 물리 시뮬레이션, 일반 비선형 제약 해결은 초기 범위 밖이다. 지원하지 않는
24
24
  관계를 그럴듯한 결과로 대체하지 않고 지원 범위를 명시한다.
25
25
 
26
- v2의 불합리한 규칙은 고칠 수 있다. 원본 자산과 확인한 의도를 보존하되, 모든 v2 동작을
27
- 보존하는 것은 요구사항이 아니다. 관계 없는 단독 상자도 정상 모델이며, 미완성 초안의
28
- 저장을 금지하지 않는다.
29
-
30
- ## 2. 현재 문제의 증거와 진단 교정
31
-
32
- | 근거 | 확인한 사실 | 한계 |
33
- | --- | --- | --- |
34
- | `src/sizing.ts` | anchor는 Figure 기준 상자의 축에 대한 위치·크기 반응 | 부품의 개구부·부착 좌표계를 직접 참조하지 못한다 |
35
- | `src/types.ts`, `src/grouping.ts`, `jointOriginPosition` | parent/joint와 부모를 따라 변하는 관절 원점은 이미 있다 | 관계가 전혀 없는 것이 아니라 크기 배치 관계가 부족하다 |
36
- | `src/gate.ts: spansAt/meets/clumpsOf` | AABB의 연결 개수 증가를 검사 | 실제 접촉·개구부 맞춤·어떤 부품에 연결됐는지 보장하지 못한다 |
37
- | `src/gate.ts: remedyFor` | 다른 덩어리의 느리게 커지는 부품을 골라 조언 | 실제 맞댄 면을 증명하지 않고 수정 방향을 단정할 수 있다 |
38
- | `src/gate.ts: sizingReport` | 기본적으로 기준 크기와 7종 2배 검사 | 연속 치수 범위·축소·운동 상태의 증명이 아니다 |
39
- | anchor 후보 탐색 | 제한된 수정 조합을 탐색 | 실패했다고 v2로 해결 불가능한 것은 아니다 |
40
- | `src/grouping.ts`, things-scene `figure-real-object.ts` | 재질·운동 frame·크기 반응으로 병합/분리 | 관계 추가가 무료 성능을 보장하지 않는다 |
41
-
42
- 드라이룸에서 복합 수정안이 생성되지 않은 것은 관찰했다. 고정 revision의 원본을 확보해
43
- 원인과 모든 대안을 검증한 상태는 아니다. 앞서 탐색 실패를 곧바로 모델 표현력의 불가능성으로
44
- 설명한 것은 과도했다. v3 설계의 근거는 일반적인 조립 의도와 일관된 배치 계산의 필요성이다.
26
+ 관계 없는 단독 상자도 정상 모델이며, 미완성 초안의 저장을 금지하지 않는다.
27
+
28
+ ## 2. 설계의 근거
29
+
30
+ v3 설계의 근거는 일반적인 조립 의도와 일관된 배치 계산의 필요성이다. 배치는 기준 상자의 축만이
31
+ 아니라 부품의 개구부·부착 좌표계를 직접 참조해야 한다. 검사는 상자의 연결 개수가 아니라 실제
32
+ 접촉·맞춤을, 몇 가지 배율이 아니라 선언한 치수 범위·축소·운동 상태를 다뤄야 한다. 관계를
33
+ 추가한다고 성능 이득이 보장되지는 않는다.
45
34
 
46
35
  ## 3. 형식적 문제 정의
47
36
 
@@ -195,7 +184,7 @@ geometry provider는 shape spec과 함께 해당 feature binding을 반환해야
195
184
  compile:
196
185
  schema/units/IDs → single writer check → resolve references
197
186
  → dependency DAG → cycle diagnostic → topological order
198
- → affine coefficients → domain obligations → prepared blueprint
187
+ → affine coefficients → domain obligations → Prepared
199
188
 
200
189
  resolve(d):
201
190
  check d∈D → evaluate dimensions/features/rest frames
@@ -317,7 +306,7 @@ inspectResolved(prepared, resolved, profile): Inspection
317
306
 
318
307
  Prepared는 식/순서/컨테이너/feature binding/geometry spec/운동 구조를,
319
308
  Resolved는 실제 크기/rigid frame/feature/slot/port를 반환한다.
320
- source version, blueprint version, evaluator semantic version을 분리한다.
309
+ source version, Prepared version, evaluator semantic version을 분리한다.
321
310
  2D·3D는 같은 Resolved를 소비하고 관계 의미를 재구현하지 않는다.
322
311
 
323
312
  compile은 자산 변경 시, layout은 치수 변경 시, pose는 상태 변경 시 처리한다.
@@ -339,7 +328,7 @@ driver는 실행 의미이므로 advisory로 무시할 수 없다. required/advi
339
328
 
340
329
  | 상태 | 처리 |
341
330
  | --- | --- |
342
- | 불명 참조/순환/중복 writer | 실행 불가 진단, 작업 초안 보관 가능, 정상 blueprint로 배포 불가 |
331
+ | 불명 참조/순환/중복 writer | 실행 불가 진단, 작업 초안 보관 가능, 정상 자산으로 배포 불가 |
343
332
  | domain 밖/음수 치수 | 해당 크기 사용 불가; 마지막 정상 표시라면 현재 결과가 아님을 명시 |
344
333
  | required assertion 위반 | 선언한 지원 범위에서 발행 불가 |
345
334
  | required assertion 미증명 | 위반 확정과 구분; 보장 정책/범위 조정 또는 추가 검증 필요 |
@@ -369,11 +358,9 @@ AI와 자동 수정은 typed edit API를 사용한다. 후보에 baseRevision/so
369
358
  전역 차단 항목이 0이어야만 수정을 제공하는 조건은 폐기한다. 국소 해결과 남은 문제를 구분한다.
370
359
  의미/외형을 훼손하며 검사만 통과한 후보를 안전하다고 표시하지 않는다.
371
360
 
372
- ## 16. 전환과 구현 단계
361
+ ## 16. 식별자와 구현 단계
373
362
 
374
- id는 불변, label은 표시 이름이다. 기존 name 기반 외부 참조는 자산 전환 시 갱신한다. V2 호환 alias 실행 계층을 유지하는 것은 요구사항이 아니다.
375
- 변환은 원본/hash 보존, v3 후보, 기계적 변환, 의도 선택, 미검증 진단을 반환한다.
376
- v2의 잘못된 동작을 보존할 의무는 없다. 의도적으로 바뀐 외형은 승인된 차분으로 기록한다.
363
+ id는 불변, label은 표시 이름이다. 기존 name 기반 외부 참조는 자산 재작성 시 갱신한다.
377
364
  초안 저장과 발행 조건을 분리한다. 미지원 consumer에 v3를 부분 해석해 보내지 않는다.
378
365
 
379
366
  세부 구현 순서와 출하 조건은 [상위 레이아웃 계약 §12](v3-layout-system.md#12-출하-전-단계와-완료-기준)를 따른다.
@@ -45,9 +45,9 @@
45
45
 
46
46
  ## 제품 연결 시 주의점
47
47
 
48
- 현재 figure-ui의 `client/modeller/figure-source.ts`는 장면 파트를 저장용 V2 source로 변환한다. `figure-inspector.ts`와 `figure-modeller-page.ts`의 update-part/update-draft 경로도 V2 draft/parts 중심이다. V3 graph를 이 경로의 `parts`로 가장해 전달하면 원본이 이중화된다.
48
+ 현재 figure-ui의 `client/modeller/figure-source.ts`는 장면 파트를 저장용 이전 형식 source로 변환한다. `figure-inspector.ts`와 `figure-modeller-page.ts`의 update-part/update-draft 경로도 이전 형식의 draft/parts 중심이다. V3 graph를 이 경로의 `parts`로 가장해 전달하면 원본이 이중화된다.
49
49
 
50
- 제품 모델러·저장·AI·렌더러를 V3 단일 경로로 전환한다. 기존 자산은 변환 또는 재작성한다. V2 병행 운영이나 호환 분기는 만들지 않는다. 제품에서는 src에서 빌드한 공용 API를 사용한다.
50
+ 제품 모델러·저장·AI·렌더러를 V3 단일 경로로 전환한다. 기존 자산은 V3로 재작성한다. 호환 분기는 만들지 않는다. 제품에서는 src에서 빌드한 공용 API를 사용한다.
51
51
 
52
52
  ## 아직 해결하지 않은 범위
53
53
 
@@ -11,13 +11,13 @@
11
11
  - 브라우저: `node docs/prototypes/render-full-dryroom.mjs --conveyor`
12
12
  - 전체 테스트: `node --test docs/prototypes/v3-*.test.mjs`
13
13
 
14
- ## V2와 의도적으로 달라진 점
14
+ ## 끝 롤러 공간을 먼저 확보하는 이유
15
15
 
16
- 기본 길이 2,400mm에서 V2 반복 공식은 일반 롤러 16개를 중심 간격 150mm로 생성한다. 양 끝 일반 롤러 중심은 ±1,125mm이며, 별도의 굵은 끝 롤러 중심은 ±1,140mm다.
16
+ 끝 공간을 따로 두지 않고 일반 롤러를 중심 간격 150mm로 길이 전체에 깔면, 기본 길이 2,400mm에서 일반 롤러는 16개다. 양 끝 일반 롤러 중심은 ±1,125mm이며, 별도의 굵은 끝 롤러 중심은 ±1,140mm다.
17
17
 
18
18
  일반 롤러 반지름 48mm, 끝 롤러 반지름 75mm이고 중심 거리는 15mm이므로, `15 + 48 < 75`다. 일반 롤러 길이 520mm도 끝 롤러 길이 560mm보다 작다. 즉 **일반 롤러 두 개가 끝 롤러 내부에 완전히 묻힌다.** 축 방향 회전으로 이 문제는 해결되지 않는다.
19
19
 
20
- V3는 양쪽 끝 공간을 먼저 확보한다.
20
+ 그래서 양쪽 끝 공간을 먼저 확보한다.
21
21
 
22
22
  ```
23
23
  끝 여백 = 끝 롤러 중심의 가장자리 거리 60 + 반지름 75 + 최소 간격 10 = 145mm
@@ -302,7 +302,7 @@ AI: 동일 typed transaction, sourceHash 사전조건, atomic apply, undo, 변
302
302
  | L1 production kernel | schema/DAG/measure/allocate/arrange | cycle, writer, NaN, overflow, 결정성, fuzz |
303
303
  | L2 geometry/motion | 모든 지원 primitive의 치수·feature 계약 | bounds/개구부/normal, shear, pose 합성 |
304
304
  | L3 modeller | 계층 편집·미리보기·undo·AI transaction | 원클릭 편집, 동시 변경, 오류 복구 E2E |
305
- | L4 consumer/전환 | v2→v3 후보, 2D/3D 공통 결과 | 실제 드라이룸/롤 프레스, 회귀 스냅샷 |
305
+ | L4 consumer | 2D/3D 공통 결과 | 실제 드라이룸/롤 프레스, 회귀 스냅샷 |
306
306
  | L5 비용/범위 | 1/100/500 instance, 범위 검증 | p95·draw call·메모리 실측, 증명 분류 |
307
307
 
308
308
  r1은 레이아웃 규칙의 검토 가능한 기준안이다. 전체 v3 출하 명세/구현이 끝났다는 뜻이 아니다.
@@ -234,7 +234,6 @@ UI는 원인 입력을 수정하는 후보를 만들 수 있지만 evaluator가
234
234
  - 값 DAG/feature frame은 반드시 scene node로 만들지 않는다. compile에서 정리한다.
235
235
  - compile은 저작 변경, dimension resolve는 크기 변경, pose는 상태 변경 때 수행한다.
236
236
  - 동일 치수/재질/운동의 병합·공유 가능성을 실제 renderer에서 측정한다.
237
- - v2→v3 변환은 원본 보존, id/외부 binding 대응, 외형 차분, 의도 선택을 포함한다.
238
237
  - 새 Figure는 독립 상태에서 시작하고 실패한 후보는 기존 장면에 덧붙이지 않는다.
239
238
  - typed edit는 sourceHash 검사, 원자 적용, undo, 재송신 멱등성을 갖춘다.
240
239
  - 발행 정책은 invalid / assertion violation / unverified / advisory를 구별한다.
@@ -2,12 +2,8 @@
2
2
 
3
3
  Status: **contract, fixed 2026-09-22; revised the same day after the chief architect's review; implemented the
4
4
  same day** (ADR-0087 decision 2, ruling 4). Implementation: `src/v3-graph.ts` (`axis-turn@1`, `axis-slide@1`),
5
- `src/v3-driver.ts` (`driverValue`, `driverStates`, `validateV3Drivers`), the motion part of `src/v3-from-v2.ts`
6
- (`planMotion`), tests in `src/v3-motion.test.ts`. The converter and every consumer use this contract; none of them
7
- defines motion on its own. Not yet covered by the converter: prismatic joints (none in the live assets; V2 scaled
8
- their travel by the instance, the contract does not), scale channels (refused as shape deformation), a rotation
9
- channel with a pivot off the part centre, channels that vary more than one axis, and parameter curves other than
10
- two linear keys. The review changed three things: the direction of every transform is written down, continuous
5
+ `src/v3-driver.ts` (`driverValue`, `driverStates`, `validateV3Drivers`), tests in `src/v3-motion.test.ts`. Every
6
+ consumer uses this contract; none of them defines motion on its own. The review changed three things: the direction of every transform is written down, continuous
11
7
  rotation is split into an accumulated amount and a phase, and drivers carry an explicit time convention.
12
8
 
13
9
  ## What a person sees
@@ -19,7 +15,7 @@ moves. Setting the value back to its default puts the arm exactly where it was d
19
15
 
20
16
  On a board, the same arm has the same six values. A connector writes them; the arm follows. Stretching the arm's
21
17
  instance size does not change where its joints turn: a joint sits on the part that carries it and moves with that
22
- part under sizing, as ADR-0066 decision 3 already says for V2.
18
+ part under sizing.
23
19
 
24
20
  ## One rule for how a child moves, with every direction written down
25
21
 
@@ -37,15 +33,15 @@ part under sizing, as ADR-0066 decision 3 already says for V2.
37
33
  - `jointParent` and `jointChild` are two named frames per joint, `<joint>.onParent` and `<joint>.onChild`. They
38
34
  **coincide at `q = 0`**: `motion(0)` is the identity, and the two frames are placed so that the child then sits in
39
35
  its rest pose.
40
- - A part with no joint to its parent has `motion = identity` (V2 `FigurePart.parent` without a joint).
41
- - The parent and child joint frames come from the rest pose and sizing (`jointOriginPosition`), so a joint moves
36
+ - A part with no joint to its parent has `motion = identity`.
37
+ - The parent and child joint frames come from the rest pose and sizing, so a joint moves
42
38
  with the part that carries it when the instance is resized.
43
39
  - `attach@1` already computes `A × O × inverse(B)`, and its type checker refuses a chain whose frames do not meet.
44
40
  No parent-chain node is added; `compose@1` and `attach@1` are enough.
45
41
 
46
- **`q = 0` is the rest pose. That is not the same claim as "the default is the rest pose."** A V2 parameter whose
47
- default is not 0 drew its rest pose at that default. The converter keeps V2's meaning: the joint frames are placed
48
- for the drawn pose, and the state default stays what V2 stored. It never subtracts the default or forces it to 0.
42
+ **`q = 0` is the rest pose. That is not the same claim as "the default is the rest pose."** A state input's default
43
+ may be non-zero. The joint frames are placed for the pose at `q = 0`, and the state default is stored as authored;
44
+ nothing subtracts the default or forces it to 0.
49
45
 
50
46
  ## Joint kinds, their state input, and the two motion operators
51
47
 
@@ -64,10 +60,10 @@ axis-slide@1 args [ax, ay, az (ratio), q (length)] params { from, to }
64
60
  - An arbitrary axis is carried as three numbers and turned with axis–angle. It is **never** collapsed into one
65
61
  Euler component.
66
62
 
67
- | V2 `FigureJoint.type` | state input | unit stored | `motion(q)` |
63
+ | joint type | state input | unit stored | `motion(q)` |
68
64
  |---|---|---|---|
69
- | `revolute` | one angle, `role: 'state'`, V2 limits as range | rad (an input may declare `deg`; the kernel converts) | `axis-turn@1` |
70
- | `prismatic` | one length, `role: 'state'`, V2 limits as range | mm | `axis-slide@1` |
65
+ | `revolute` | one angle, `role: 'state'`, joint limits as range | rad (an input may declare `deg`; the kernel converts) | `axis-turn@1` |
66
+ | `prismatic` | one length, `role: 'state'`, joint limits as range | mm | `axis-slide@1` |
71
67
  | `continuous` | one **phase** angle, `role: 'state'`, range `[−180°, 180°)` | rad | `axis-turn@1` |
72
68
 
73
69
  ### Continuous rotation: an amount and a phase, not a limit
@@ -86,16 +82,15 @@ values first would interpolate 350 → 10 and turn the wrong way. Widening the r
86
82
 
87
83
  ## Parameters
88
84
 
89
- A V2 `FigureParameter` keeps its **name, unit, range, default and target**. In V3 it is the state input itself:
90
- `id` = the parameter name, `unit` and `min`/`max` from the range, `role: 'state'`, and the asset's `stateDefaults`
91
- carries the default as V2 stored it. Its target is the joint (or part channel) whose `motion(q)` reads it. A
92
- parameter that V2 let drive several joints becomes one input read by several `axis-turn@1` nodes: one writer, many
93
- readers, which the graph already allows.
85
+ A parameter is a state input: `id` is its name, `unit` and `min`/`max` give its range, `role: 'state'`, and the
86
+ asset's `stateDefaults` carries its default. Its target is the joint (or part channel) whose `motion(q)` reads it. A
87
+ parameter that drives several joints is one input read by several `axis-turn@1` nodes: one writer, many readers,
88
+ which the graph already allows.
94
89
 
95
90
  ## Drivers: the asset declares, figure-model evaluates, the consumer owns time
96
91
 
97
- The kernel has **no clock**. Same inputs, same result, always. A V2 `AnimationClip` becomes a **driver**
98
- declaration on the asset, not a graph node. Responsibilities:
92
+ The kernel has **no clock**. Same inputs, same result, always. An animation is a **driver** declaration on the asset,
93
+ not a graph node. Responsibilities:
99
94
 
100
95
  | who | does |
101
96
  |---|---|
@@ -119,9 +114,7 @@ drivers: [{
119
114
  ```
120
115
 
121
116
  - **Time convention, one for all drivers.** `time.unit` is seconds; `duration` is the clip length in seconds; every
122
- key `at` is a fraction of the duration in `[0, 1]`. The converter normalises V2's two habits (seconds in
123
- `duration`, fractions in `at`, ADR-0051 ②) into exactly this and records the source form in the report. A driver
124
- with a missing or non-positive duration is refused.
117
+ key `at` is a fraction of the duration in `[0, 1]`. A driver with a missing or non-positive duration is refused.
125
118
  - **Loop end.** `wrap`: time `t` maps to `t mod duration`, and the sample at `duration` equals the sample at 0 (the
126
119
  last key's value is reached at `at = 1` and the next period starts from key 0). For an accumulating driver, wrapping
127
120
  adds the full-period delta to the accumulated value, so the roller keeps turning rather than snapping back.
@@ -131,45 +124,17 @@ drivers: [{
131
124
  source per state** explicitly (`driver` or `external`). Values are never summed and "last call wins" is not a
132
125
  rule. figure-model's validation refuses two drivers naming the same state.
133
126
 
134
- A V2 part channel (`path: 'rotation'` on a part with no joint, like the conveyor roller) converts to a continuous
135
- state input on that part, `<part>.spin`, driving an `axis-turn@1` about the part's own axis, and a driver for the
136
- clip with `accumulates: true`. The conveyor's rotation animation is therefore carried, not lost.
137
-
138
- ## What the converter does with the seven motion assets
139
-
140
- 1. For every `FigureJoint`: two joint frames from the rest pose and sizing, one state input, one `axis-turn@1` or
141
- `axis-slide@1`, one `attach@1`, and the child's `place@1` reads the attached pose.
142
- 2. For every part with `parent` and no joint: `compose@1` of the parent's pose and the rigid offset.
143
- 3. For every `parameter`: a state input as above; refuse (`PARAMETER_TARGET`) if its target is not a joint or a part
144
- channel this contract covers.
145
- 4. For every animation clip: a driver, with the clip's channels mapped to state inputs; refuse
146
- (`ANIMATION_CHANNEL`) for a channel path this contract does not cover (`scale`).
127
+ A part that spins without a joint (the conveyor roller) has a continuous state input on that part, `<part>.spin`,
128
+ driving an `axis-turn@1` about the part's own axis, and a driver with `accumulates: true`.
147
129
 
148
130
  ## Two corrections to earlier wording
149
131
 
150
132
  - **Rigid does not mean affine in the state.** A rigid pose is affine in the spatial coordinates it maps, but a
151
133
  rotation matrix is a sin/cos function of the joint angle. So `axis-turn@1` is **not affine in `q`**, and the
152
134
  operator-list guard is restated: `geomean@1` is the only node that is non-affine in the *design inputs*;
153
- `axis-turn@1` is non-affine in a *state input*. The 8-size comparison therefore stays a limited regression check for
154
- the rest pose and does not become a proof of equivalence under motion.
135
+ `axis-turn@1` is non-affine in a *state input*.
155
136
  - **Rotation is compared as a relative angle**, the angle of `R₂ · R₁ᵀ`, never as a difference of Euler components.
156
137
 
157
- ## "The same" for motion (completion criterion)
158
-
159
- For every converted motion asset, V2 and V3 are compared at the base size and the doubled sizes with:
160
-
161
- - each joint at its default, its mid-point and both limits;
162
- - **parent and child joints changed at the same time**, not one at a time;
163
- - at least one joint with an arbitrary (non-principal) axis;
164
- - a non-zero default, checked at the default and away from it;
165
- - a continuous joint just before and just after the phase boundary (e.g. 179° and 181°);
166
- - a looping driver just before and just after the loop end (e.g. 0.99 and 1.01 durations), and a `hold` driver past
167
- its end.
168
-
169
- Same part list, same centre and extents within 1 mm, same relative rotation within 0.1°. For each driver, the state
170
- value at time 0, half and end must equal V2's clip value at those times. Exact conversion and deliberate
171
- re-authoring are marked separately in the report.
172
-
173
138
  ## Not in this contract
174
139
 
175
140
  - Inverse kinematics, reach checks, collision between links. Consumers may add them on top; the kernel does not.
@@ -2,11 +2,11 @@
2
2
 
3
3
  > 현재 구현·검증·제품 연결 상태는 [V3 통합 현황](v3-status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
5
- > 최신 작업 기준: [V3 단일 체계 전환](v3-cutover.md). 아래의 V2 유지 및 버전별 제품 연결 방향은 과거 작업 기록이며, 향후 설계 조건에서 제외한다.
5
+ > 최신 작업 기준: [V3 단일 체계 전환](v3-cutover.md). 아래의 버전별 제품 연결 방향은 과거 작업 기록이며, 향후 설계 조건에서 제외한다.
6
6
 
7
7
  ## 이번 이관 범위
8
8
 
9
- 실험용 JSON 입력 검사와 오류 계약을 `src/v3-json.ts`, `src/v3-contract.ts`의 strict TypeScript 소스로 옮겼다. 기본 빌드에서 JS·타입 선언을 생성하며 기존 package 루트에서 이름이 구분된 API로 내보낸다. package.json·의존성·V2 validate/compile 구현은 변경하지 않았다.
9
+ 실험용 JSON 입력 검사와 오류 계약을 `src/v3-json.ts`, `src/v3-contract.ts`의 strict TypeScript 소스로 옮겼다. 기본 빌드에서 JS·타입 선언을 생성하며 기존 package 루트에서 이름이 구분된 API로 내보낸다. package.json·의존성은 변경하지 않았다.
10
10
 
11
11
  | 공개 API | 책임 |
12
12
  |---|---|
@@ -15,7 +15,7 @@
15
15
  | v3Utf8ByteLength | 브라우저와 서버의 동일 UTF-8 바이트 계산 |
16
16
  | V3ContractError | code/path를 가진 공통 오류 |
17
17
 
18
- **parseV3Json은 모델 의미 검증기가 아니다.** JSON 형식이 올바른 V2 객체도 읽을 수 있다. V3 자산 버전·단위·그래프·appearance 검사는 여전히 자산 컴파일러가 담당한다. 공개된 이름이 전체 V3 지원이나 안정화 완료를 뜻하지 않는다.
18
+ **parseV3Json은 모델 의미 검증기가 아니다.** JSON 형식만 올바르면 V3 자산이 아닌 객체도 읽는다. V3 자산 버전·단위·그래프·appearance 검사는 여전히 자산 컴파일러가 담당한다. 공개된 이름이 전체 V3 지원이나 안정화 완료를 뜻하지 않는다.
19
19
 
20
20
  ## 단일 구현 유지
21
21
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  2026-09-21:
29
29
 
30
- - `npm test`: 기존 V2 회귀 및 새 입력 경계 검사 포함 **442개 통과**.
30
+ - `npm test`: 당시 소스 전체와 새 입력 경계 검사 포함 **442개 통과**.
31
31
  - `node --test docs/prototypes/v3-*.test.mjs`: **143개 통과**.
32
32
  - `npm run build`: 성공, JS와 타입 선언 생성.
33
33
  - `node docs/prototypes/v3-runtime-browser.mjs`: 실제 Chrome에서 빌드된 ESM 실행 성공. Buffer/process가 없는 상태에서 한글·이모지 UTF-8 계산과 이스케이프 중복 키 거부 확인.
@@ -36,7 +36,7 @@
36
36
 
37
37
  ## 그래프 평가기 이관
38
38
 
39
- `src/v3-graph.ts`에 기존 평가기 전체를 옮기고 루트 API에 `compileV3Graph`를 추가했다. V2의 `compile`은 변경하지 않았다. `src/v3-graph-types.ts`는 입력/상수/노드, 단위·차원, 좌표계 타입, 형상·배치, 평가 결과와 컴파일된 계획의 공개 타입을 정의한다.
39
+ `src/v3-graph.ts`에 기존 평가기 전체를 옮기고 루트 API에 `compileV3Graph`를 추가했다. `src/v3-graph-types.ts`는 입력/상수/노드, 단위·차원, 좌표계 타입, 형상·배치, 평가 결과와 컴파일된 계획의 공개 타입을 정의한다.
40
40
 
41
41
  실험 진입점 `docs/prototypes/v3-typed-plan.mjs`는 두 export만 남긴 호환 어댑터다. 단위 계산·위상 정렬·좌표계 검사·형상 생성·반복 배치의 구현을 두 벌 유지하지 않는다. 기존 연산자 버전과 실패 코드를 유지했다.
42
42
 
@@ -62,7 +62,7 @@
62
62
  | v3-asset.ts | compileV3Asset, parseV3Asset, serializeV3Asset, captureV3AuthoredInputs |
63
63
  | v3-asset-types.ts | 문서·자산·재질·binding 선언 및 평가 결과 타입 |
64
64
 
65
- 버전 식별자는 기존 실험 버전을 유지한다. 정식 소스에 포함되었다고 파일 포맷 안정화나 제품 저장 허용을 선언하지 않는다. package.json과 V2 compile/validate는 변경하지 않았다.
65
+ 버전 식별자는 기존 실험 버전을 유지한다. 정식 소스에 포함되었다고 파일 포맷 안정화나 제품 저장 허용을 선언하지 않는다. package.json은 변경하지 않았다.
66
66
 
67
67
  유지한 계약:
68
68
 
@@ -104,4 +104,4 @@ workspace 이관 당시 검증: 소스 테스트 **451개**, V3 실험 테스트
104
104
 
105
105
  핵심 입력·그래프·capability·자산·편집 workspace는 src로 이관했다. 제품의 저장·발행·편집·렌더러를 V3 단일 체계로 전환해야 하며, 제품 연결은 아직 미완료다. 파일 저장 실험도 제품 DB 구현으로 간주하지 않는다. 접촉/충돌 연구 모듈과 대표 모델 생성기는 계속 실험 영역에 있다.
106
106
 
107
- 다음 단계는 V3 단일 저작·저장·렌더링 경로의 제품 연결이다. 기존 자산은 V3 기준으로 바꾼다. V2 병행 운영을 위한 경로는 추가하지 않는다. 제품 API 배포는 아직 수행하지 않았다.
107
+ 다음 단계는 V3 단일 저작·저장·렌더링 경로의 제품 연결이다. 기존 자산은 V3 기준으로 바꾼다. 제품 API 배포는 아직 수행하지 않았다.
@@ -3,7 +3,7 @@
3
3
  > 현재 구현·검증·제품 연결 상태는 [V3 통합 현황](v3-status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
5
5
  2026-09-20. [1차 검사](v3-sample-validation.md)의 후속이다.
6
- **현재 v2 함수 검사 / 명시한 v3 의도 후보 계산 / 기구학 수치 실험**을 구별한다.
6
+ **당시 기존 함수 검사 / 명시한 v3 의도 후보 계산 / 기구학 수치 실험**을 구별한다.
7
7
  테스트가 통과했다는 것은 아래 관찰이 재현됐다는 뜻이며, 결함이 해결됐다는 뜻이 아니다.
8
8
  제품 소스, 서버, 원본 샘플을 변경하지 않았다.
9
9
 
@@ -41,7 +41,7 @@ node --test docs/prototypes/v3-samples.test.mjs
41
41
 
42
42
  ### 로봇암: 좌표계는 가능하지만 링크 크기 변경은 별도 문제
43
43
 
44
- v2의 position과 joint.origin은 정지 모델 좌표다. 이를 부모-relative offset과
44
+ 원본 샘플의 position과 joint.origin은 정지 모델 좌표다. 이를 부모-relative offset과
45
45
  자식-local pivot으로 변환한 뒤 `parent · rest · joint`로 계산했다.
46
46
  관절 양쪽에서 계산한 동일 pivot의 월드 위치가 일치하는지 검사했다.
47
47
  단순히 부품 중심 거리가 일정하다는 검사보다 구체적인 기구학 불변량이다.
@@ -1,15 +1,14 @@
1
1
  # V3 shape-dimension contract — per-axis radii, elliptic sections, and dimensions that follow state
2
2
 
3
3
  Status: **contract, fixed 2026-09-22 by the V3 designer** (ADR-0087, stage-review rulings 3 and 5). §1 (per-axis
4
- dimensions) implemented the same day: `cylinder-shape@2`, `rounded-box@2`, `hollow-box@2`, the converter writing
5
- them, the sampler reading them. §2–3 implemented the same night: dependence on state is tracked per value in the
4
+ dimensions) implemented the same day: `cylinder-shape@2`, `rounded-box@2`, `hollow-box@2`, and the sampler reading
5
+ them. §2–3 implemented the same night: dependence on state is tracked per value in the
6
6
  compiler and printed as `dependence` beside `outputTypes`; every operator carries a `state` policy (`follows` or
7
7
  `refuses`; an operator without one fails with `STATE_POLICY`); `occupancy.extent` may not depend on state
8
8
  (`OCCUPANCY_STATE`); `inspectV3Asset` covers the state range and reports `proven` (corners; no state-dependent
9
9
  rotation), `sampled` (a rotation depends on state; corners plus per-input sweeps, step stated, not a proof) or
10
10
  `defaults` (one state, as asked). The `bounded` method of §3 (swept envelope) is not implemented; such assets report
11
- `sampled`. The converter writes a two-key scale channel as dimensions × affine(parameter) for percent or ratio
12
- parameters (`STATE_DIMENSION` note). This is one rule for what a shape's dimensions are and what they may depend on. It is not three
11
+ `sampled`. This is one rule for what a shape's dimensions are and what they may depend on. It is not three
13
12
  exceptions bolted on for the three assets that needed them.
14
13
 
15
14
  ## What a person sees
@@ -19,11 +18,11 @@ cradle gets thinner: its radius shrinks, its length does not. The edge plates th
19
18
  stay on its surface. Nothing else moves. At 0 % the roll is exactly the drawn roll.
20
19
 
21
20
  An author stretches a kiosk to twice its width. The bezel's corners stay the same round in front view where the
22
- section did not stretch, and go wider than they are tall where it did, because V2 drew them that way. Nothing pops
23
- back to a circle on its own.
21
+ section did not stretch, and go wider than they are tall where it did, because each corner radius follows its own
22
+ axis. Nothing pops back to a circle on its own.
24
23
 
25
24
  An author opens the worker figure and stretches it sideways. The legs, which the author chose not to keep round,
26
- become oval like V2 drew them. The renderer draws the oval it is given. It never "fixes" it to a circle.
25
+ become oval. The renderer draws the oval it is given. It never "fixes" it to a circle.
27
26
 
28
27
  ## 1. Every round thing has per-axis dimensions
29
28
 
@@ -32,7 +31,7 @@ A section is round only when its two cross dimensions are equal. The providers w
32
31
  | operator | evaluated provider | dimensions | round when | status |
33
32
  |---|---|---|---|---|
34
33
  | `cylinder-shape@2` | `ellipticCylinder` | `radiusX`, `radiusZ`, `length` (axis Y) | `radiusX = radiusZ` | implemented 2026-09-22 |
35
- | `frustum-shape@2` | — | `topRadiusX`, `topRadiusZ`, `bottomRadiusX`, `bottomRadiusZ`, `height` | pairs equal | not implemented; no V2 shape needs it (V2 draws no frustum) |
34
+ | `frustum-shape@2` | — | `topRadiusX`, `topRadiusZ`, `bottomRadiusX`, `bottomRadiusZ`, `height` | pairs equal | not implemented |
36
35
  | `sphere-shape@1` | `sphere` | `radiusX`, `radiusY`, `radiusZ` (already so) | all equal | implemented |
37
36
  | `rounded-box@2` | `roundedBoxXZ` | `width`, `height`, `depth`, `roundX`, `roundZ` | `roundX = roundZ` | implemented 2026-09-22 |
38
37
  | `hollow-box@2` | `hollowBoxXZ` | as `hollow-box@1` with `roundX`, `roundZ` in place of `round` | same | implemented 2026-09-22 |
@@ -41,38 +40,32 @@ The evaluated provider name is distinct from the `@1` one so a renderer can neve
41
40
  single radius. Features on `ellipticCylinder` are `start` and `end` only; `radialBottom` has no single meaning on an
42
41
  ellipse.
43
42
 
44
- - `@1` of each provider stays valid and means the equal case; the converter writes `@2` only where V2 drew an
45
- unequal section. Nothing that exists today is invalidated.
43
+ - `@1` of each provider stays valid and means the equal case. `@2` is needed only for an unequal section.
46
44
  - **The renderer draws what it is given.** An elliptic section is an ellipse; an elliptic corner is the quadratic
47
45
  Bézier from `(hw − roundX, −hd)` to `(hw, −hd + roundZ)` with the corner `(hw, −hd)` as control point, which is
48
- what things-scene's `roundedRect` draws and what `src/v3-mesh-compare.ts` samples. It is not a circular or
49
- elliptic arc: an arc differs from the Bézier by up to 0.06 × round at the corner's middle. A renderer that snaps a
50
- section to a circle is wrong, and the mesh-level comparison of the completion criterion is what catches it.
46
+ what things-scene's `roundedRect` draws. It is not a circular or elliptic arc: an arc differs from the Bézier by
47
+ up to 0.06 × round at the corner's middle. A renderer that snaps a section to a circle is wrong.
51
48
  - Validity: every dimension positive (a corner radius may be 0), `roundX ≤ width/2`, `roundZ ≤ depth/2`. A
52
- declared corner larger than the section is refused at compile; the converter normalises V2's declared value to
53
- what V2 drew (ruling 2) and reports it.
49
+ declared corner larger than the section is refused at compile.
54
50
 
55
- ### What `keepRound` becomes
51
+ ### A round section under non-uniform sizing
56
52
 
57
- V2's `keepRound` was a rule about **what happens to a round section under non-uniform sizing**: keep it round by
58
- the geometric mean of the two factors. In V3 that is not a flag; it is two possible expressions for two dimensions:
53
+ What happens to a round section when the instance is sized differently along its two cross axes is not a flag. It
54
+ is two possible expressions for two dimensions:
59
55
 
60
56
  ```
61
- kept round radiusX = radiusZ = r₀ · geomean(fx, fz) (V2 keepRound on, the default)
62
- follows axes radiusX = r₀ · fx, radiusZ = r₀ · fz (V2 keepRound off)
57
+ kept round radiusX = radiusZ = r₀ · geomean(fx, fz)
58
+ follows axes radiusX = r₀ · fx, radiusZ = r₀ · fz
63
59
  ```
64
60
 
65
- Both are ordinary length expressions. The converter chooses by the V2 flag and writes the expression; the
66
- `ELLIPTIC_SECTION` refusal is now a note on the report (implemented 2026-09-22), and `CORNER_PER_AXIS` notes a
67
- corner written with two semi-axes. The same two shapes apply to corners: V2 scaled a rounded box's finished mesh
68
- per axis, so a corner drawn with radius `r₀` becomes `roundX = r₀·fx`, `roundZ = r₀·fz`. That is what
69
- `ROUND_NOT_SCALED` (40 parts in the last run) was reporting as a loss; with `@2` corners it is no longer a loss.
61
+ Both are ordinary length expressions; the author chooses which one to write. The same two shapes apply to corners:
62
+ a corner drawn with radius `r₀` that follows the axes is `roundX = r₀·fx`, `roundZ = r₀·fz`.
70
63
 
71
64
  ## 2. A dimension may depend on state
72
65
 
73
66
  A state input (`role: 'state'`) may be an argument of a shape or length node. A consumed roll's radius is
74
67
  `r₀ − fed · k`; a lamp's glow radius grows with the gate. This is the core-semantics §2 invariant ("state changes the
75
- actual shape dimension") made concrete, and it is what a V2 `scale` channel means: `dims × affine(p)`.
68
+ actual shape dimension") made concrete. A dimension that scales with a state input is `dims × affine(p)`.
76
69
 
77
70
  ### Dependence is tracked in the type, apart from unit and frame
78
71
 
@@ -114,30 +107,14 @@ range, and says how it knows:
114
107
  - The endpoint-only shortcut is **wrong** for a rotating long part: inside at both end angles, out in the middle.
115
108
  That case takes the `bounded` row, not the `proven` one.
116
109
 
117
- ## 4. What the converter writes with this contract
118
-
119
- - V2 cylinder with `keepRound: false` and differing cross factors → `cylinder-shape@2` with `radiusX`, `radiusZ`
120
- following their own axes. WORKER converts; `ELLIPTIC_SECTION` is retired.
121
- - V2 rect or hollow rect whose section follows the instance → `rounded-box@2` / `hollow-box@2` with per-axis corner
122
- radii `r·fx`, `r·fz`. `ROUND_NOT_SCALED` is retired.
123
- - V2 `scale` channel on a part, two linear keys, from a parameter → each scaled dimension becomes
124
- `dim × affine(p)`; the part's centre is unchanged (V2 scaled about the part centre). The dependence set of every
125
- affected length includes the parameter. DRY_ROOM's lamps and ROLL_CRADLE's roll convert; `ANIMATION_CHANNEL`
126
- stays only for channels the contract does not cover (pivots, compound axes, curves other than two linear keys).
127
- - The report marks every state-dependent dimension it wrote, and the occupancy inspection of a converted asset
128
- runs over the parameter ranges as §3 says.
129
-
130
- ## 5. Completion criterion for this contract
110
+ ## 4. Completion criterion for this contract
131
111
 
132
112
  - The four providers' `@2` forms compile, evaluate, and their `@1` forms are unchanged (the operator-list guard
133
113
  gains the four names).
134
114
  - Dependence appears in `outputTypes`; a synthetic new operator with no policy fails to compile when handed a
135
115
  state-dependent value; `fit-pitch@1` and `fixed-count@1` still refuse it; a shape provider accepts it.
136
- - The three assets convert and match V2 at box level at 8 sizes × states, and the WORKER's legs match as ovals.
137
116
  - An occupancy check on the roll cradle reports `proven` over the `fed` range, and a synthetic rotating long part
138
117
  reports `bounded`, with a violation found at a middle angle that the endpoints alone would have missed.
139
- - The mesh-level comparison of the completion criterion covers sections and corners, so an elliptic section drawn
140
- round would fail it.
141
118
 
142
119
  ## Not in this contract
143
120
 
@@ -145,10 +122,9 @@ range, and says how it knows:
145
122
  - Shape deformation beyond dimensions: bending, twisting, free-form profiles that change with state. Those would be
146
123
  new providers with their own contracts.
147
124
 
148
- ## 4. 표시 여부 (visibility) — 안 보이는 것과 작은 것은 다르다
125
+ ## 5. 표시 여부 (visibility) — 안 보이는 것과 작은 것은 다르다
149
126
 
150
- **V3 설계자 결정 2026-09-22.** V2 는 부품을 숨길 때 크기를 0 으로 만든다. V3 는 그 방법을 쓰지
151
- 않는다. 0 을 아주 작은 양수로 바꾸는 것도 쓰지 않는다 — 확대·그림자·포인터 선택에서 차이가
127
+ **V3 설계자 결정 2026-09-22.** 부품을 숨기려고 크기를 0 으로 만들지 않는다. 0 을 아주 작은 양수로 바꾸는 것도 쓰지 않는다 — 확대·그림자·포인터 선택에서 차이가
152
128
  생기고, 「안 보임」이 「아주 작음」이 되어 버린다.
153
129
 
154
130
  ### 규칙
@@ -172,19 +148,9 @@ range, and says how it knows:
172
148
 
173
149
  | 목록 | 무엇이 들어가는가 | 누가 쓰는가 |
174
150
  | --- | --- | --- |
175
- | `v3WorldBoxesOf` | 그리는 몸통만 (`visible === false` 제외) | renderer, V2 비교 |
151
+ | `v3WorldBoxesOf` | 그리는 몸통만 (`visible === false` 제외) | renderer |
176
152
  | `v3OccupiedBoxesOf` | 놓인 몸통 전부 (숨긴 것 포함) | occupancy 검사 |
177
153
 
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
154
  ## When `proven` may be said (added 2026-09-23, after two counterexamples from the V3 designer)
189
155
 
190
156
  `proven` means the gate evaluated the corners of the state box and nothing else. That is only a proof when every
package/docs/v3-status.md CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  > **2026-09-22 — this file is a progress note, not the document of record.** The decision and the order of work are
4
4
  > `operato-application/adr/ADR-0087.md`; the current state is the app's
5
- > `operato-application/packages/operato-figure/docs/CURRENT_STATUS.md` (section "figure-model V2 → V3"). Where this
5
+ > `operato-application/packages/operato-figure/docs/CURRENT_STATUS.md`. Where this
6
6
  > file disagrees with those two, they win.
7
7
 
8
8
  기준일: 2026-09-21. **현재 상태를 판단하는 문서다.** 개별 연구·검증 문서의 과거 단계 수치나 '다음 단계'보다 이 현황을 우선한다.
9
9
 
10
10
  ## 제품 방향
11
11
 
12
- 모델러·AI·저장·렌더러를 **V3 하나로 전환**한다. V2 호환성·병행 운영은 요구사항이 아니다. 기존 자산은 V3로 변환하거나 재작성한다. 원본 복구본은 데이터 교체를 위한 안전 조치일 뿐 V2 실행 지원을 뜻하지 않는다. [전환 결정](v3-cutover.md)
12
+ 모델러·AI·저장·렌더러는 **V3(`figure-v3-asset-1`) 하나**를 쓴다. V2와 V1은 2026-09-26 코드와 명세에서 제거했다(ADR-0087 전환을 제거로 마쳤다). [전환 결정](v3-cutover.md)
13
13
 
14
14
  ## 완료 범위와 미완료 범위
15
15
 
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## 검증 근거
29
29
 
30
- - `npm test`: 소스 **455개 통과**. 기존 검사와 신규 V3 검사를 합친 수치이며 V3 기능 수가 아니다.
30
+ - `node --test src/*.test.ts`: 소스 **286개 통과**(2026-09-26, V2·V1 제거 뒤). 시험 수이며 V3 기능 수가 아니다.
31
31
  - `node --test docs/prototypes/v3-*.test.mjs`: 실험 **143개 통과**.
32
32
  - `npm run build`: 타입 선언·JS 생성 성공.
33
33
  - `node docs/prototypes/v3-runtime-browser.mjs`: Chrome에서 빌드된 모듈 실행, 네 대표 모델(드라이룸·컨베이어·로봇암·포크리프트)의 계산값/형상/재질/binding을 Node와 비교.
@@ -43,8 +43,8 @@
43
43
  4. [자산 계약](v3-asset-persistence.md), [저장 검증](v3-storage-integration.md).
44
44
  5. [V3 단일 전환](v3-cutover.md).
45
45
 
46
- 다음 작업은 모델러의 사용자 동작·AI·실제 저장·렌더링을 이 공용 V3 구현에 연결하는 것이다. 완료한 계층을 다시 이관하거나 V2 호환 분기를 추가하는 것이 아니다.
46
+ 다음 작업은 모델러의 사용자 동작·AI·실제 저장·렌더링을 이 공용 V3 구현에 연결하는 것이다.
47
47
 
48
48
  ## 제품 연결 작업 추가 기록 (2026-09-21)
49
49
 
50
- things-factory의 `packages/figure-ui/docs/v3-product-cutover.md`에 제품 전환 진행 상태를 기록했다. V3 제품 세션·캔버스·편집 컴포넌트·저장 API와 페이지를 작성했으나, 기본 route는 아직 교체하지 않았다. 로컬 활성 자산32개가 모두 V2이며 자산 전환·AI 도구·발행/소비처 연결이 남아 있다. UI241개 테스트 및 클라이언트/서비스 타입 검사 통과는 V3 제품 end-to-end 검증 완료를 뜻하지 않는다. 운영 DB 데이터 변경이나 배포는 하지 않았다.
50
+ things-factory의 `packages/figure-ui/docs/v3-product-cutover.md`에 제품 전환 진행 상태를 기록했다. V3 제품 세션·캔버스·편집 컴포넌트·저장 API와 페이지를 작성했으나, 기본 route는 아직 교체하지 않았다. 당시 로컬 활성 자산 32개는 아직 V3로 전환되지 않았고, 자산 전환·AI 도구·발행/소비처 연결이 남아 있었다. UI241개 테스트 및 클라이언트/서비스 타입 검사 통과는 V3 제품 end-to-end 검증 완료를 뜻하지 않는다. 운영 DB 데이터 변경이나 배포는 하지 않았다.
@@ -12,9 +12,9 @@
12
12
 
13
13
  ## 왜 기존 저장 API에 바로 연결하지 않았는가
14
14
 
15
- 제품 figure-service의 `figure-source.ts`는 V2 validate/compile/costOf/scoreOf를 호출한다. `figure-mutation.ts`의 생성·수정·되돌리기는 이 검증을 사용하며 발행은 V2 검사 경로를 사용한다. figure-ui도 draft/parts를 직접 편집한다.
15
+ 당시 제품 figure-service의 `figure-source.ts`는 이전 형식의 검증·컴파일·비용·점수 함수를 호출했다. `figure-mutation.ts`의 생성·수정·되돌리기와 발행도 그 검사 경로를 사용했다. figure-ui도 draft/parts를 직접 편집한다.
16
16
 
17
- 실험 포맷만 허용하면 저장은 되지만 기존 소비자가 읽을 수 없는 자산이 된다. 따라서 **제품 V3 경로 연결은 아직 미완료**다. 실험 코드의 직접 import, package.json 변경, V2 검증 우회는 하지 않았다.
17
+ 실험 포맷만 허용하면 저장은 되지만 기존 소비자가 읽을 수 없는 자산이 된다. 따라서 **제품 V3 경로 연결은 아직 미완료**다. 실험 코드의 직접 import, package.json 변경, 기존 검증 우회는 하지 않았다.
18
18
 
19
19
  ## 저장 계약
20
20
 
@@ -51,8 +51,8 @@ scope/id 조합을 해시한 파일명으로 사용해 사용자 식별자를
51
51
  ## 제품 연결 순서
52
52
 
53
53
  1. 입력·그래프·capability·문서·자산·편집 workspace의 정식 소스 이관은 [완료](v3-runtime-extraction.md)했다. 이 모듈들을 제품에 반영한다.
54
- 2. figure-service의 저장·조회·발행을 V3 단일 기준으로 전환하고 저장 revision CAS를 구현한다. V2 병행 운영은 요구사항이 아니다.
55
- 3. figure-ui와 렌더러·AI를 V3 단일 경로에 연결한다. 기존 자산은 V3로 변환하거나 재작성한다.
54
+ 2. figure-service의 저장·조회·발행을 V3 단일 기준으로 전환하고 저장 revision CAS를 구현한다.
55
+ 3. figure-ui와 렌더러·AI를 V3 단일 경로에 연결한다. 기존 자산은 V3로 재작성한다.
56
56
  4. 실제 인증·DB로 두 편집 세션의 충돌, 저장 실패, 재접속, 모델 전환, AI 늦은 응답을 검증한다.
57
57
 
58
58
  이 문서의 완료 범위는 저장 경계의 실행 가능한 검증이며, 제품 연결·배포 완료가 아니다.