@hatiolab/figure-model 0.3.2 → 0.3.4

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 (71) hide show
  1. package/README.md +15 -1
  2. package/dist/asset-types.d.ts +47 -6
  3. package/dist/asset-types.d.ts.map +1 -1
  4. package/dist/asset.d.ts +11 -0
  5. package/dist/asset.d.ts.map +1 -1
  6. package/dist/asset.js +24 -10
  7. package/dist/asset.js.map +1 -1
  8. package/dist/authoring-actions.d.ts.map +1 -1
  9. package/dist/authoring-actions.js +8 -4
  10. package/dist/authoring-actions.js.map +1 -1
  11. package/dist/capabilities.js +28 -4
  12. package/dist/capabilities.js.map +1 -1
  13. package/dist/gate.d.ts +14 -2
  14. package/dist/gate.d.ts.map +1 -1
  15. package/dist/gate.js +45 -25
  16. package/dist/gate.js.map +1 -1
  17. package/dist/graph-types.d.ts +93 -7
  18. package/dist/graph-types.d.ts.map +1 -1
  19. package/dist/graph.d.ts +7 -0
  20. package/dist/graph.d.ts.map +1 -1
  21. package/dist/graph.js +380 -66
  22. package/dist/graph.js.map +1 -1
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/joint-list.d.ts.map +1 -1
  28. package/dist/joint-list.js +5 -3
  29. package/dist/joint-list.js.map +1 -1
  30. package/dist/kernel-version.d.ts +1 -1
  31. package/dist/kernel-version.js +1 -1
  32. package/docs/assembly-constraints.md +3 -3
  33. package/docs/asset-persistence.md +8 -8
  34. package/docs/authoring-transactions.md +2 -2
  35. package/docs/capability-bindings.md +1 -1
  36. package/docs/common-kernel-experiment.md +2 -2
  37. package/docs/completeness-review-2026-10-04.md +405 -0
  38. package/docs/core-semantics.md +2 -2
  39. package/docs/cutover.md +4 -4
  40. package/docs/document-contract.md +3 -3
  41. package/docs/dryroom-profile-validation.md +1 -1
  42. package/docs/editor-integration.md +4 -4
  43. package/docs/evaluation-changes-design.md +304 -0
  44. package/docs/evaluation-performance-summary-2026-10-05.md +218 -0
  45. package/docs/full-conveyor-validation.md +8 -8
  46. package/docs/full-dryroom-validation.md +5 -5
  47. package/docs/full-forklift-validation.md +5 -5
  48. package/docs/full-robot-validation.md +4 -4
  49. package/docs/layout-system.md +2 -2
  50. package/docs/legacy-risk-audit-2026-09-23.md +14 -14
  51. package/docs/minimal-model.md +3 -3
  52. package/docs/openusd-review.md +14 -14
  53. package/docs/operator-contracts.md +4 -4
  54. package/docs/prototypes/README.md +2 -2
  55. package/docs/prototypes/asset.schema.json +1 -1
  56. package/docs/prototypes/document.schema.json +1 -1
  57. package/docs/prototypes/editor-browser.mjs +2 -2
  58. package/docs/prototypes/linear-comparison.py +1 -1
  59. package/docs/prototypes/render-dryroom.py +1 -1
  60. package/docs/prototypes/render-full-dryroom.mjs +12 -12
  61. package/docs/prototypes/runtime-browser.mjs +3 -3
  62. package/docs/research-review.md +1 -1
  63. package/docs/review-2026-09-22.md +19 -19
  64. package/docs/runtime-extraction.md +10 -10
  65. package/docs/sample-validation-expanded.md +5 -5
  66. package/docs/sample-validation.md +5 -5
  67. package/docs/shape-dimension-contract.md +1 -1
  68. package/docs/spatial-contracts.md +2 -2
  69. package/docs/status.md +12 -1
  70. package/docs/storage-integration.md +5 -5
  71. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- # V3 자산 저장·복원 계약 — 실험판
1
+ # Figure 자산 저장·복원 계약 — 실험판
2
2
 
3
3
  > 현재 구현·검증·제품 연결 상태는 [구현·검증 현황](status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
@@ -11,19 +11,19 @@
11
11
  - [네 대표 모델 자산 생성 예제](prototypes/asset-samples.mjs)
12
12
  - [검증 테스트](prototypes/asset.test.mjs)
13
13
 
14
- 버전은 `figure-asset-1`이다. **정식 Figure V3 전체 포맷 완료를 뜻하지 않는다.**
14
+ 버전은 `figure-asset-1`이다. **정식 Figure 전체 포맷 완료를 뜻하지 않는다.**
15
15
 
16
16
  ## 저장 항목
17
17
 
18
18
  | 항목 | 의미 |
19
19
  |---|---|
20
- | document | 기존 typed graph + capability V3 선언 |
20
+ | document | 기존 typed graph + capability 선언 |
21
21
  | designInputs | 현재 설계 입력값. 선언된 단위 그대로 저장 |
22
22
  | stateDefaults | 다시 열 때 사용할 명시적인 동작 기본값 |
23
23
  | palette | 사용한 토큰의 색상 snapshot. 미정의 토큰은 거부 |
24
24
  | appearance | source placement/member별 재질과 품질 설정 |
25
25
  | name | 자산 이름 |
26
- | occupancy | 선언한 점유 부피(`extent`: 그래프의 길이 값 셋)와 놓이는 면(`placement`). ADR-0044·0045 를 V3 로 다시 세운 것(ADR-0087 결정 3). 없으면 `inspectFigureAsset` 이 `OCCUPANCY_UNDECLARED` 를 낸다 |
26
+ | occupancy | 선언한 점유 부피(`extent`: 그래프의 길이 값 셋)와 놓이는 면(`placement`). ADR-0044·0045 를 지금 형식으로 다시 세운 것(ADR-0087 결정 3). 없으면 `inspectFigureAsset` 이 `OCCUPANCY_UNDECLARED` 를 낸다 |
27
27
  | document.capabilities.names | 도형 수준 능력 이름. `FIGURE_CAPABILITIES` 목록 그대로 검사한다(ADR-0051 ④) |
28
28
 
29
29
  메시·계산된 pose·과거 성공 판정은 저장하지 않는다. 다시 로드하면 선언에서 재계산한다. 이 프로토타입은 팔레트 색상을 고정 snapshot으로 저장한다. 제품 공통 테마를 따라갈지 자산 색상을 유지할지는 별도 정책이 필요하다. 조명·환경맵·카메라는 이 계약에 포함되지 않아 모든 화면의 픽셀 동일성을 보장하지 않는다.
@@ -42,7 +42,7 @@ appearance는 모든 `place@1`/`member@1`에 정확히 하나씩 필요하다.
42
42
 
43
43
  원기둥 분할 수는 현재 지원 preset으로 명시 저장한다. 사용자에게 분할 수 직접 입력 UI를 추가하지 않았다. 다른 provider에 임의의 segments를 붙이는 것은 거부한다. 재질은 token/preset/transparent/emissive 범위를 지원하며 미지원 필드를 조용히 버리지 않고 거부한다.
44
44
 
45
- 부품에 붙이는 capability 메타데이터는 정본에 자리를 두지 않는다(2026-09-23). 자리만 남겨 두면 검증된 binding과 구별되지 않기 때문이다. `slot`을 `seat`/`grip`으로 자동 해석하지도 않는다. 이 의미를 V3로 가져오려면 검증되는 binding으로 다시 저작한다.
45
+ 부품에 붙이는 capability 메타데이터는 정본에 자리를 두지 않는다(2026-09-23). 자리만 남겨 두면 검증된 binding과 구별되지 않기 때문이다. `slot`을 `seat`/`grip`으로 자동 해석하지도 않는다. 이 의미를 지금 형식으로 가져오려면 검증되는 binding으로 다시 저작한다.
46
46
 
47
47
  ## 검증 결과
48
48
 
@@ -59,10 +59,10 @@ appearance는 모든 `place@1`/`member@1`에 정확히 하나씩 필요하다.
59
59
 
60
60
  ## 남은 제품화 범위
61
61
 
62
- - 예제 생성기는 V3로 이미 재저작한 모델을 포장한다. 범용 마이그레이터가 아니다.
63
- - 원본에 없는 기능을 복원했다고 주장하지 않는다. 기존 자산은 V3로 재작성하며, 실제 데이터 교체 전에 대상과 복구본을 확인한다.
62
+ - 예제 생성기는 지금 형식으로 이미 재저작한 모델을 포장한다. 범용 마이그레이터가 아니다.
63
+ - 원본에 없는 기능을 복원했다고 주장하지 않는다. 기존 자산은 지금 형식으로 재작성하며, 실제 데이터 교체 전에 대상과 복구본을 확인한다.
64
64
  - 네 대표 모델을 넘는 provider/material 확장, 입력 라벨/저작 메타데이터 등은 정식 포맷에서 추가 정의해야 한다.
65
65
  - 파일 시스템 원자적 교체·권한·DB revision 충돌·업로드/다운로드 API는 없다. 함수는 문자열을 반환하며 제품 저장소에 직접 쓰지 않는다.
66
66
  - 실제 속성창·AI-dock·새 모델/다른 모델 전환·재접속은 통합 테스트가 남아 있다.
67
67
 
68
- 저장된 자산을 공통 편집 세션으로 여는 연결과 격리 UI 검증은 [편집 화면 연결 검증](editor-integration.md)에 기록했다. 다음 게이트는 실제 제품의 V3 전용 편집 경로와 저장 API 연결이다. 프로토타입 테스트 수를 제품 배포 완료 여부로 사용하지 않는다.
68
+ 저장된 자산을 공통 편집 세션으로 여는 연결과 격리 UI 검증은 [편집 화면 연결 검증](editor-integration.md)에 기록했다. 다음 게이트는 실제 제품의 편집 경로와 저장 API 연결이다. 프로토타입 테스트 수를 제품 배포 완료 여부로 사용하지 않는다.
@@ -6,7 +6,7 @@
6
6
 
7
7
  [입력 세션](../src/authoring-session.ts)은 design/state 값 편집을 담당한다. [workspace](../src/editor-workspace.ts)는 상자 추가와 입력 편집을 같은 50단계 undo/redo 이력으로 관리한다. [검증 테스트](prototypes/authoring-session.test.mjs)는 정식 소스 구현을 호출한다. 격리 UI에는 새 Figure·상자 추가·undo/redo 버튼이 연결되어 있다. 제품 AI-dock·일반 그래프 재저작·삭제·재질 편집은 아직 미완료다.
8
8
 
9
- 세션은 V3 문서와 초기 입력을 받아 동일 compiler로 검증한다. UI/AI 모두 아래 같은 요청을 사용한다.
9
+ 세션은 Figure 문서와 초기 입력을 받아 동일 compiler로 검증한다. UI/AI 모두 아래 같은 요청을 사용한다.
10
10
 
11
11
  ```json
12
12
  {
@@ -54,4 +54,4 @@ read/preview/apply 반환값과 초기 선언은 내부 상태와 분리한다.
54
54
 
55
55
  세션은 메모리에서 동작한다. 자산 저장 계약과 설계 값/일시적 동작의 분리 정책은 구현했고, 격리 HTTP 파일 저장·복원·충돌 검증도 통과했다. 제품 DB·재접속·다중 사용자 편집, 제품 UI의 preview 승인과 AI 명령 변환·권한 검사는 남아 있다.
56
56
 
57
- 다음 제품화 단계는 실제 모델러·AI·저장·렌더러를 이 공통 V3 경로로 전환하는 것이다. 완료된 저장 계약을 다시 설계하는 단계가 아니다.
57
+ 다음 제품화 단계는 실제 모델러·AI·저장·렌더러를 이 공통 경로로 전환하는 것이다. 완료된 저장 계약을 다시 설계하는 단계가 아니다.
@@ -104,6 +104,6 @@ region은 의미상 허용/검사 영역이다. 표시용 가이드가 될 수
104
104
  4. 지원하는 평면 feature만 허용한다: box의 6면, roundedBox의 위/아래, cylinder의 start/end.
105
105
  5. feature 중심과 바깥쪽 법선을 실제 placement 변환으로 계산한다. 기존 feature frame 축은 provider 축이므로 법선과 혼동하지 않는다.
106
106
 
107
- 반환된 `verified-planar-surface`는 **출처가 검증된 평면이라는 뜻**이지 payload와 접촉했다는 뜻이 아니다. 정점/면 영역 전체, 허용 오차, 하중 지지, 충돌 여부는 별도 검사다. curved feature, portal 등의 아직 정의되지 않은 경우를 가짜 평면으로 추정하지 않고 거부한다. 단일 repeat의 선택된 평면은 V3 계약에서 지원한다.
107
+ 반환된 `verified-planar-surface`는 **출처가 검증된 평면이라는 뜻**이지 payload와 접촉했다는 뜻이 아니다. 정점/면 영역 전체, 허용 오차, 하중 지지, 충돌 여부는 별도 검사다. curved feature, portal 등의 아직 정의되지 않은 경우를 가짜 평면으로 추정하지 않고 거부한다. 단일 repeat의 선택된 평면은 이 계약에서 지원한다.
108
108
 
109
109
  [직렬화 계약 및 테스트 결과](document-contract.md)를 함께 참조한다.
@@ -1,4 +1,4 @@
1
- # 공통 v3 실행 커널 실험
1
+ # 공통 실행 커널 실험
2
2
 
3
3
  > 현재 구현·검증·제품 연결 상태는 [구현·검증 현황](status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
@@ -109,7 +109,7 @@ rect는 box로 축약한 **배치 검증용 proxy**다. 모서리 외형 보존
109
109
  2. 반복 위치와 개별 형상 운동의 순서는 명세에 반드시 들어가야 한다.
110
110
  3. geometry도 상태를 입력받아야 한다. 강체 frame에만 상태를 넣는 r2는 부족하다.
111
111
  4. 단일 상속으로 포크리프트를 표현할 수 있지만 기존 flat 채널의 자동 변환은 별도 문제다.
112
- 5. **이 실험으로 v3 설계 완성을 선언할 수 없다.** proxy/부분 모델을 전체 fidelity 변환으로
112
+ 5. **이 실험으로 Figure 설계 완성을 선언할 수 없다.** proxy/부분 모델을 전체 fidelity 변환으로
113
113
  확대하고, feature/단위/반복 subtree/참조 정책을 정리한 뒤 렌더러 비교가 필요하다.
114
114
 
115
115
  다음 검증 우선순위는 geometry feature와 반복 subtree를 공통 계약에 포함하는 것이다.
@@ -0,0 +1,405 @@
1
+ # figure-model 완결성·확장성 점검
2
+
3
+ 점검일: 2026-10-04. 대상: 작업 트리가 깨끗한 `4610edc`(패키지 0.3.2).
4
+ 코드와 공개 계약을 검토하고 `npm test` 및 별도 경계 사례를 실행했다.
5
+ 런타임 코드를 변경하거나 발행하지 않았다. 설치된 제품 및 npm 배포 상태는 이번 점검 범위가 아니다.
6
+
7
+ ## 해결 현황 (2026-10-04, figure-model 0.3.3 · things-scene 10.1.72 발행)
8
+
9
+ F1~F4를 고쳤다. 항목마다 기존 코드에서 실패하는 회귀 시험을 먼저 넣었고, 고친 뒤 `npm test`(타입 검사와
10
+ 725개 시험)와 build가 통과한다. 재현 스크립트는 이제 네 항목 모두 거절 또는 정상 결과를 출력한다.
11
+ `npm run test:coverage`의 기준(줄 100%, 분기 95%)은 점검 시점 HEAD에서도 못 미쳤다(줄 96.87%, 분기 89.24%).
12
+ 고친 뒤 줄 96.91%, 분기 89.50%이며 새 코드에 덮이지 않은 줄은 없다.
13
+
14
+ | 항목 | 커밋 | 선택한 계약 | 시험 |
15
+ | -------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
16
+ | F1 결과 소유권 | `122b3c0` | 인스턴스 evaluator 결과의 values 레코드와 geometry 목록은 호출자 소유이고, 그 안의 값과 body는 동결된다. 쓰기는 TypeError로 거절된다. 내부 슬롯은 동결하지 않은 비공개 객체로 두고, 결과에는 값이 바뀔 때 만든 동결 사본을 넣는다. 형상은 제자리에서 동결한다. 바뀌지 않은 형상·body는 같은 객체로 이어진다. 전체 평가(`plan.evaluate`)는 아무것도 보관하지 않으므로 결과 전체가 호출자 소유이고 동결하지 않는다. pose step의 `bodies`·`poses`는 호출자 사본이고, `read`·`changed`는 같은 evaluator의 다음 호출 전까지만 답하며 그 뒤에는 `STEP_EXPIRED`로 거절된다. | `src/result-ownership.test.ts` |
17
+ | F2 공개 검증 | `c86d74b` | `compileFigureAsset`이 관절 목록, 레코드 schema, 레코드와 노드 일치를 검사한다. serialize·parse는 같은 검사를 거친다. 저작 명령은 관절 노드를 레코드에서 다시 쓰므로 입구와 명령 중간의 자세 읽기에는 관절 검사 없는 내부 컴파일(`compileFigureAssetUnjointed`, 패키지 밖으로 내보내지 않음)을 쓰고, 명령 결과는 공개 compile로 검사한다. | `src/asset-entrances.test.ts` |
18
+ | F3 깊은 그래프 | `4f32334` | 컴파일 순회가 자체 스택을 쓴다. 선언 순서 깊이 우선이라는 순서, 타입·단위·상태 거절, 순환 경로 표기(들어선 곳부터)가 이전과 같다. 같은 재귀가 있던 gate의 design 의존성 계산도 컴파일 순서 한 번 훑기로 바꿨다. `compileFigureGraph`는 자체 크기 한도가 없고, byte·노드 한도는 document·asset 입구의 계약이다. | `src/graph-depth.test.ts` |
19
+ | F4 검사 옵션 | `9a332e7` | 설정 오류는 Figure를 컴파일하기 전에 `INSPECT_OPTION`(경로 `options.<키>`)으로 거절한다. `sweepSteps`는 2 이상 정수, `maxCornerInputs`는 0~12 정수다. 실행할 상태 수를 첫 평가 전에 세어 8,192를 넘으면 `INSPECT_BUDGET`으로 거절한다. 코너 열거는 32비트 시프트를 쓰지 않는다. 한도는 `FIGURE_INSPECT_LIMITS`로 내보낸다. | `src/inspect-options.test.ts` |
20
+
21
+ 비용(Node, 호출당, 점검 HEAD 대비):
22
+
23
+ - F1: 무변경 평가, pose step, 전체 평가는 그대로다(로봇 6축 0.55 / 9.7 / 61 µs). evaluate 경로에서 바뀐 값의
24
+ 사본 비용이 붙는다. 컨베이어 상태 1개 7.4→13.7 µs, 로봇 관절 1개 4.8→9.6 µs, 관절 7개 12→20 µs,
25
+ 설계값 1개 30→52 µs(롤러 컨베이어). fixture의 상태 입력 대부분은 pose step을 타므로 프레임마다 도는 경로는
26
+ 늘지 않는다. 슬롯을 제자리에서 동결하는 방식도 쟀으나 숫자 배열이 박싱되어 pose step 9.8→15 µs,
27
+ 관절 7개 evaluate 12→35 µs로 더 느려 채택하지 않았다.
28
+ - F2: 관절이 있는 자산의 compile이 parse와 같은 비용이 된다(hinged-door 2.3→5.7 ms, parse 6.4 ms 그대로).
29
+ 저작 명령 하나에 약 3 ms가 붙는다.
30
+
31
+ 장면 프레임(things-scene `FigureRig.update`, Node CPU, 렌더러 GPU 제외, 600프레임 평균, 발행판 0.3.2 → 수정본):
32
+
33
+ | 장면 | 0.3.2 | 수정본 | 증가 |
34
+ | ----------------------------------------------------------- | ----------------- | -------------------------- | --------- |
35
+ | 컨베이어 60대, clip 회전(pose step) | 0.34 ms | 0.37 ms | +5~10% |
36
+ | 로봇 20대, 관절 전부 움직임(pose step) | 0.29 ms | 0.31 ms | +5% |
37
+ | 지게차 20대, 상태 전부 움직임(pose step) | 0.38~0.43 ms | 0.39~0.42 ms | 차이 없음 |
38
+ | 문+램프 60대, 램프 flag를 넘나드는 제어(매 프레임 evaluate) | 0.22 ms | 0.34~0.37 ms | +55% |
39
+ | 컨베이어 60대, 크기 끌기(매 프레임 evaluate·형상 변경) | 4.05 ms (p95 6.0) | 5.6~~6.0 ms (p95 7.8~~9.1) | +40~45% |
40
+ | 로봇 20대, 관절 이동 + 10프레임마다 크기 변경 | 0.46 ms (p95 1.7) | 0.53 ms (p95 2.2) | +15% |
41
+
42
+ evaluate 경로의 증가는 사본 비용이다. 크기 변경 한 번에 롤러 컨베이어가 복사하는 객체는 약 100개(자세 25, 행 항목 22,
43
+ 경로 배열 22)이고 객체당 약 100 ns다. 회전 공유, spread 복사, 심볼 캐시를 시험했으나 줄지 않았고, 사본 중복 제거용
44
+ Map은 없애면 오히려 느려졌다(10.4→15.9 µs). 더 줄이려면 body·행 항목을 제자리에서 동결하고 자세만 복사하는 설계
45
+ 변경이 필요하며, 행의 pose step 경로가 동결 자세를 읽게 되는지 따로 검토해야 한다. 발행 전 판단 대상이다.
46
+
47
+ 소비자 검증(things-scene 10.1.71 작업 트리, `test/tsconfig.figure-model-src.json`으로 형제 소스를 연결해 실행.
48
+ 연결 여부는 소스에만 있는 `FIGURE_INSPECT_LIMITS`로 확인):
49
+
50
+ - 단위·figure 시험 전체 2,713개: 발행판과 수정본 모두 통과. figure 시험 107개 안에 결과 조회(pose step과 evaluate
51
+ 혼합 시 모든 mesh 위치), 부착물(관절이 움직이는 부품의 place, 그리퍼 끝의 workpiece), 선택(병합·묶음 mesh의 pick이
52
+ 부품·사본으로 돌아옴), sizing(크기 변경 후 복귀, 선언 범위 밖 크기, 위에서 본 그림)이 있다.
53
+ - 타입: 빌드된 `dist/*.d.ts`에 대해 things-scene을 검사했다. 인스턴스 결과를 readonly로 표현하기 전에는 오류 0개,
54
+ 표현한 뒤에는 오류 2개다. 둘 다 쓰기가 아니라 선언 타입 문제다. `figure-rig.ts`의 `movePoses` 인자 타입이
55
+ `places`의 자세를 가변 배열로 선언했고, `dress`가 `result.bindings`를 가변 `FigureAssetEvaluation`으로 돌려준다.
56
+ things-scene이 이 판으로 올릴 때 두 선언에 `readonly`를 붙여야 한다. 이번 작업에서는 소비자를 고치지 않았다.
57
+ - things-factory·operato-application 소스에는 인스턴스 evaluator 사용처가 없다.
58
+
59
+ API 호환성 영향:
60
+
61
+ - 인스턴스 evaluator 결과의 중첩 객체에 쓰던 호출자는 TypeError를 받는다. things-scene의 `FigureRig`는 결과를 쓰지
62
+ 않고 pose step의 `read`·`changed`도 같은 단계 안에서만 부른다(capabilities 래퍼 포함).
63
+ - 관절 레코드와 노드가 어긋난 자산은 이제 compile·serialize에서도 거절된다. parse가 이미 거절하던 자산이다.
64
+ render-batches 시험 하나가 그런 자산을 손으로 만들고 있어 관절이 읽지 않는 치수를 늘리도록 고쳤다.
65
+ - 검사 옵션의 잘못된 값은 이전의 계산 오류나 빈 sweep 대신 `INSPECT_OPTION`으로 거절된다. 기본값 결과는 같다.
66
+ - 추가된 공개 이름: `FIGURE_INSPECT_LIMITS`, 오류 코드 `STEP_EXPIRED`·`INSPECT_OPTION`·`INSPECT_BUDGET`,
67
+ 타입 `FigureFrozen`·`FigureInstanceEvaluation`·`FigureCapabilityInstanceEvaluation`.
68
+ - 인스턴스 evaluator의 `evaluate`와 evaluate로 끝난 `advance`의 결과 타입이 위 instance 타입으로 바뀐다(내용이 deep
69
+ readonly). 전체 평가 타입은 그대로다. 소유 계약은 README 「Evaluation results: who owns what」에 적었다.
70
+
71
+ 남은 제한:
72
+
73
+ - evaluate 경로의 사본 비용(위 수치). 값 레코드를 읽을 때만 사본을 만드는 방식은 레코드 복사·직렬화 의미를
74
+ 바꾸므로 보류했다.
75
+ - `compileFigureGraph`를 직접 부르는 호출자는 그래프 크기를 스스로 책임진다. 전체 평가 예산(geometry 합계,
76
+ slot 수)은 정하지 않았다.
77
+ - 검사 예산 8,192 상태는 상태 수 한도이고 시간·메모리 상한은 아니다.
78
+ - 아래 「확장성」, 「검사·성능 숫자의 해석」 항목은 이번 범위가 아니다.
79
+
80
+ ### 발행 판정 (2026-10-04)
81
+
82
+ - 정확성 수정은 유지하고, 성능 비용을 밝힌 채 발행을 준비한다. 발행 설명은 「정확성·검증 경계 개선, 일반 평가
83
+ 비용 증가」다. 자세 경로 성능 개선이나 렌더링 성능 유지로 설명하지 않는다.
84
+ - 성능 검증은 CPU rig 측정까지만 했다. 브라우저 전체 프레임 목표를 충족하는지는 확인하지 않았다.
85
+ - readonly 타입은 유지한다. things-scene `4b21c72c`가 두 선언(`movePoses`·`dress`, 그리고 자세를 행렬로 읽는
86
+ `pose`·`matrixOf`)을 읽기 전용으로 고쳤다. figure-model 0.3.2와 새 소스 모두에 대해 타입 검사 오류가 0개이고,
87
+ 단위·figure 시험 2,787개가 둘 다에서 통과한다. things-scene의 figure-model 범위는 발행 뒤에 올린다.
88
+ - 복사 구조 재설계(대규모 resize 최적화)는 별도 과제다. 결과 소유권 결함을 다시 열지 않고, 브라우저 전체 프레임의
89
+ 중앙값·p95를 기준으로 진행한다.
90
+
91
+ ### 제자리 동결 후보의 측정 결과 (2026-10-04): 효과 미확인, 채택하지 않음
92
+
93
+ 시험한 설계는 다음과 같다. 어떤 노드도 읽지 않는 행(롤러 컨베이어의 롤러 행)은 사본·경로·자세를 행 연산이 새로
94
+ 만들고 이후 내부에서 다시 읽지 않는다. 따라서 이 행의 슬롯은 형상처럼 제자리에서 동결하고 복사하지 않는다.
95
+ 배치가 만드는 body 껍데기(`{ id, ...g }`)도 결과만 보유하므로 제자리에서 동결하고, 담긴 자세는 동결 사본으로 바꾼다.
96
+ 행이 읽는 자세, 배치 값, 구성원 같은 내부 계산 객체는 동결하지 않고 지금처럼 복사한다. terminal repeat의 pose step은
97
+ 읽히지 않는 행의 항목을 읽지 않으므로 동결 자세를 읽지 않는다.
98
+
99
+ - 정확성: figure-model 시험 728개, things-scene figure 시험 74개(형제 소스 연결)가 후보에서도 통과했다.
100
+ 공유 사본을 동결하지 않는 결함을 심으면 소유권 시험 5개가 실패했다.
101
+ - 사본 수: 롤러 컨베이어 크기 변경 한 번에 `publicOf` 사본이 약 100개에서 35개(자세 17, 배치 값 16, 배치 1, 중심 목록 1)로
102
+ 줄었다. 동결 횟수는 391회로 같다. 행 항목을 복사하던 일이 같은 수의 제자리 동결로 바뀌었을 뿐이며,
103
+ 숫자 배열 동결이 값을 박싱하는 비용도 그대로 남는다.
104
+ - 커널(Node, 크기 변경 step, 인스턴스당, 번갈아 3회): 51~64 → 50~58 µs, 약 5~10% 감소.
105
+ - 브라우저 전체 프레임(`things-scene/test/browser/check-figure-update-cost.mjs`, Apple GPU, 인스턴스 60개, merge on,
106
+ 워밍업 60, 600프레임, F1 적용판과 후보를 번갈아 3회. 표의 값은 frame 중앙값(p95) ms, 측정 해상도 0.1 ms):
107
+
108
+ | 장면 | F1 적용판 | 후보 |
109
+ | ------------------------------------ | ------------------------------------ | ---------------------------------- |
110
+ | 컨베이어 크기 끌기(매 프레임 evaluate) | 8.2 (10.7) · 6.9 (8.3) · 7.7 (10.3) | 7.3 (8.3) · 7.6 (8.9) · 7.5 (8.7) |
111
+ | 컨베이어 회전(pose step만) | 3.0 (4.3) · 5.0 (6.7) · 3.9 (5.3) | 3.9 (6.1) · 5.0 (6.5) · 3.8 (4.7) |
112
+ | 문+램프 flag 교차(매 프레임 evaluate) | 2.6 (4.1) · 3.8 (5.6) · 3.3 (4.0) | 3.7 (5.4) · 3.0 (4.1) · 2.9 (4.4) |
113
+
114
+ 후보의 중앙값은 F1 적용판의 회차 간 변동 범위 안에 있다. p95는 회차에 따라 줄기도 하고 늘기도 했다(크기 끌기
115
+ 10.7→8.3, 회전 4.3→6.1, flag 4.1→5.4). 개선과 악화가 섞여 있어 일관된 성능 차이를 확인하지 못했다. 크기 끌기 장면의
116
+ rig `update`(평가 포함)는 5.3~5.8 대 5.5~5.7 ms로 차이가 없다. 커널이 5~10% 줄었어도 rig와 전체 프레임의 개선이
117
+ 확인되지 않았으므로 채택 기준을 충족하지 못했다. 판정은 「효과 미확인」이며 채택하지 않았다. F1 정확성 수정은 유지한다.
118
+ 0.3.2의 값(크기 끌기 4.05 ms, p95 6.0, Node CPU rig 측정)은 참고값으로만 남긴다. 측정 방식이 달라 위 표와 직접
119
+ 비교하지 않는다.
120
+
121
+ 크기 변경 프로필(후보 기준, self time)은 `frozen` 17%, `step` 12%, `publicOf` 11%, `finite` 10%, GC 5%였다.
122
+ 사본 수를 줄이는 것만으로는 충분하지 않았다. 동결·평가·rig·렌더 비용이 남는다. 숫자 배열 동결이 다음 후보라는 것은
123
+ 프로필에서 얻은 단서이고, 공개 자세 표현을 바꾸면 빨라진다는 결론은 아니다. 크기 끌기 프레임 7~8 ms 가운데 커널은
124
+ 약 3 ms이고, rig 몫이 2.2~2.7 ms, 렌더가 1.4~1.7 ms다. 다음 순서는 공개 API 변경보다 rig의 크기 변경 처리 비용 분해다.
125
+
126
+ 이번에 남긴 것은 다음과 같다.
127
+
128
+ - `src/result-ownership.test.ts`의 시험 2개. 인스턴스가 상태 변경, 크기 변경, pose step을 거친 뒤에도 전체 평가 결과에
129
+ 동결된 객체가 없음을 확인한다. 또 크기 변경 직후 pose step이 새 구조의 행을 모두 그리고 전체 평가와 같은 자세를
130
+ 내며, 행이 읽는 배치·자세가 비동결 내부 객체임을 확인한다. 두 시험 모두 현재 코드에서 통과하고, 슬롯 동결 결함과
131
+ 전체 평가 동결 결함을 심으면 실패한다.
132
+ - things-scene 측정 스크립트의 `gate` 종류. 문+램프 Figure를 스크립트 안에서 만들고, flag가 매 프레임 문턱을 넘게 한다.
133
+
134
+ ### rig 크기 변경 비용 분해와 형상별 키 캐시 (2026-10-05): 효과 미확인, 채택하지 않음
135
+
136
+ 분해(`check-figure-update-cost.mjs`의 `BREAKDOWN=1`, 컨베이어 60대, 프레임당 60대 합계 중앙값)에서 크기 변경 프레임의
137
+ rig `update` 약 5 ms는 커널 3.0~3.3 ms, 배치 계획 0.66~0.74 ms, 병합 묶음 0.78~0.97 ms, 단독 mesh·행 0.28~0.32 ms,
138
+ `dress` 0.19~0.23 ms로 나뉜다. mesh와 instanced mesh는 새로 만들어지지 않는다. 매 프레임 새로 그리는 geometry는 60대가
139
+ 함께 쓰는 병합 geometry 3개이고, 행 버퍼 60개가 다시 올라간다. CPU 쪽 바이트는 프레임당 약 149~152 KB다. 렌더 시간은
140
+ 회전만 하는 프레임보다 길지 않았다. 현재 측정에서 GPU 업로드가 추가 병목이라는 근거는 찾지 못했다. 업로드 개수가
141
+ 같다고 바이트와 드라이버 비용까지 같다는 뜻은 아니며, 드라이버 쪽 비용은 재지 않았다. CPU 프로필에서 반복되는 일은
142
+ 모든 인스턴스의 모든 body에 대해 매 프레임 다시 만드는 문자열 키였다(batch signature, geometry 키).
143
+
144
+ 시험한 후보는 형상 부분의 키를 형상 객체마다 한 번만 계산해 `WeakMap`에 두는 것이다.
145
+
146
+ - figure-model `render-batches.ts`의 signature와 things-scene `figureGeometryKeyOf`에 적용했다. 공개 API는 바뀌지 않는다.
147
+ - 형상 부분만 캐시하고 segments·재질·표시 여부는 매번 읽는다.
148
+ - 끝까지 동결된 형상만 캐시한다. 가변 형상과 겉만 동결된 형상은 매번 계산한다.
149
+ - 값은 문자열이므로 형상이나 GPU 자원을 붙잡지 않는다.
150
+ - 시험: figure-model 3개, things-scene 3개(`test/figure-geometry-key.check.js`). 무조건 캐시, 얕은 동결 검사, 전체 signature를
151
+ 형상으로 캐시하는 결함을 심으면 실패한다. 이 시험들은 현재 코드에서도 통과하므로 남겨 두었다.
152
+
153
+ 브라우저 A/B(F1 적용판과 후보를 번갈아 3회, 인스턴스 60개, merge on, 워밍업 60, 600프레임, frame 중앙값(p95) ms).
154
+ 끌기는 매 프레임 새 크기이고 롤러 행이 24개에서 27개로 늘어난다.
155
+
156
+ | 장면 | F1 적용판 | 후보 |
157
+ | ---------------------------- | -------------------------------- | --------------------------------- |
158
+ | 컨베이어 크기 끌기(두 크기 교대) | 7.27 (9.39) · 6.63 (8.11) · 7.44 (8.99) | 7.11 (8.63) · 6.72 (8.42) · 7.79 (10.02) |
159
+ | 컨베이어 끌기(행 개수 변화) | 7.31 (9.03) · 6.74 (8.59) · 7.35 (9.17) | 6.72 (8.78) · 6.78 (8.29) · 8.20 (10.45) |
160
+ | 컨베이어 회전(pose step만) | 4.01 (4.76) · 3.61 (5.16) · 5.75 (7.39) | 4.44 (5.57) · 3.82 (5.74) · 4.39 (5.77) |
161
+ | 문+램프 flag 교차 | 2.92 (4.36) · 2.76 (4.37) · 3.93 (5.18) | 3.25 (4.92) · 4.38 (6.19) · 3.24 (4.48) |
162
+
163
+ 단계 분해(각 판 1회라 잠정값)에서 배치 계획은 0.68~0.72 → 0.48~0.49 ms로 줄었다. 병합 묶음(0.88~0.93 → 0.91~0.95 ms)과 업로드 바이트는
164
+ 같았다. 전체 프레임의 중앙값과 p95는 회차마다 개선과 악화가 섞여 있어 일관된 차이를 확인하지 못했다. 판정은
165
+ 「효과 미확인」이며 채택하지 않았다. 문+램프 장면은 batch가 없어 이 변경과 무관하며, 회차 간 변동만 보여 준다.
166
+
167
+ 측정 스크립트에 함께 남긴 것은 다음과 같다.
168
+
169
+ - `BREAKDOWN=1`: rig 단계별 시간, 새 geometry·mesh·업로드 버퍼 수와 CPU 쪽 바이트.
170
+ - `PROFILE=1`: 페이지 CPU 프로필.
171
+ - `drag` 장면.
172
+ - 시계 해상도를 µs로 올리려고 페이지를 cross-origin isolated로 띄운다.
173
+ - 행렬 수 열은 이제 매 프레임 mesh를 다시 찾는다. 개수가 늘어난 행이나 새 mesh는 그리는 사본 전부를 쓴 것으로 센다.
174
+ 이전에는 처음 찾은 mesh만 비교해서 행이 커지면 0으로 나왔다.
175
+
176
+ ### 병합 geometry 재생성 분해와 부품 geometry 재사용 (2026-10-05): 채택 보류
177
+
178
+ 측정 방식부터 바꿨다. 판을 하나씩 따로 띄워 번갈아 재는 방식은 같은 판끼리의 A/A에서 짝지은 frame 중앙값 차이가
179
+ 최대 ±1.9 ms였다(ABBA 4쌍, 다른 세션이 돌아 부하 평균 약 5). 그래서 두 판을 한 페이지에 함께 띄우고 프레임 블록을
180
+ 무작위 순서로 번갈아 도는 `things-scene/test/browser/check-figure-ab.mjs`를 만들었다. 두 판은 번들, 은행, 렌더러를
181
+ 따로 갖는다. things-scene 소스 교체는 작업 트리를 건드리지 않고 rollup에서 overlay 파일로 한다. 이 방식의 A/A
182
+ (블록 150개×10프레임, 2회)에서 블록 차이의 중앙값은 ±0.06 ms 이내, 양쪽 frame 중앙값 차이는 0.07 ms 이내였다.
183
+
184
+ frame에는 상태·크기 쓰기, rig `update`, 렌더와 1픽셀 읽기가 들어간다. 측정 스크립트의 `kernel` 열(따로 둔 evaluator)은
185
+ frame에 들어가지 않는다. 병합 geometry 생성은 `update` 안의 `drawMerged`에서 일어나고, GPU 업로드는 렌더에서 일어난다.
186
+ 따라서 frame 안에서 커널, geometry 생성, 렌더가 겹쳐 세어지지 않는다. `BREAKDOWN=1`의 단계 시간은 모두 `update` 안에
187
+ 포함된 부분이다.
188
+
189
+ 분해(Node, 롤러 컨베이어, 단위는 아래에 적음):
190
+
191
+ - 60대가 함께 쓰는 병합 geometry는 그룹 3개이고, 크기마다 그룹당 한 번만 만들어진다. 두 크기 교대와 매 프레임 새 크기
192
+ 모두 프레임마다 3개가 만들어진다. 두 크기 교대에서는 이전 크기의 마지막 사용자가 놓는 순간 entry가 해제되어 다음
193
+ 프레임에 다시 만들어진다. 이는 캐시 수명으로 줄일 수 있다. 매 프레임 새 크기에서는 생성 비용 자체를 줄여야 한다.
194
+ - 새 크기 한 번의 생성, 세 그룹 합계(= 프레임당): 228~248 µs. 이 가운데 부품 형상의 삼각형화가 160~169 µs, bounds
195
+ 계산이 34~35 µs, 정점 변환·복사·법선 계산이 17~20 µs, 버퍼 생성이 5 µs다. 같은 크기 은행 조회는 rig 하나당 6~8 µs
196
+ (형상 문자열 키 4.5, 자세 숫자 1.5)이고 60대가 매 프레임 한다.
197
+ - 크기 변경 뒤에도 같은 형상 객체인 부품이 16개 중 11개다. 그런데도 다시 삼각형화된다. 병합 geometry는 정점 사본을
198
+ 가지므로 생성이 끝나면 부품 geometry를 놓는다. 부품을 따로 그리는 mesh가 없으면 사용자가 0이 되어 바로 dispose된다.
199
+ 이것은 현재 참조 수 규칙에 따른 정상 해제다.
200
+
201
+ 후보는 참조 수명을 늘리는 최적화다. 병합 entry가 자기 부품 geometry를 entry의 수명 동안 잡고, entry를 해제할 때 놓는다.
202
+
203
+ - rig는 새 묶음을 먼저 얻고 나서 이전 묶음을 놓는다. 따라서 형상이 같은 부품은 키로 재사용되고, 형상이 바뀐 부품은
204
+ 키가 달라 새로 만들어진다.
205
+ - 부품 바이트는 여러 entry가 공유해도 한 번만 세어 은행 상한에 넣는다. 새·이전 entry가 함께 있는 순간에도 상한이
206
+ 적용된다. 생성이 거절되면 잡았던 부품을 모두 돌려준다.
207
+ - 시험 4개를 만들었다. 같은 부품 geometry 재사용과 병합 없이 그린 것과의 일치, 공유 바이트 한 번 계산과 정확한 차감,
208
+ 공존 순간의 상한과 거절 시 반환, 반복 크기 변경·생성·최종 dispose 뒤 회수다. 처음 셋은 HEAD에서 실패하고 후보에서
209
+ 통과한다. 해제 누락, 거절 시 미반환, 공유 바이트 중복 계산 결함을 심으면 각각 실패한다.
210
+ - Node에서 새 크기 생성은 세 그룹 합계 248 → 150 µs다(그룹별 142/30/76 → 96/22/32 µs).
211
+
212
+ 브라우저(한 페이지 교차, 인스턴스 60개, 블록 150개×10프레임, frame 중앙값(p95) ms, B−A는 블록 차이의 중앙값):
213
+
214
+ | 장면 | 회차 1 | 회차 2 | 회차 3 | 회차 4 |
215
+ | ------------------------ | ------------------------------- | ------------------------------- | ------------------------------- | -------------------------------- |
216
+ | 크기 교대 A→B | 7.43 (9.49) → 7.26 (9.23), −0.16 | 6.84 (8.77) → 6.74 (8.57), −0.13 | 6.92 (8.81) → 6.87 (9.10), −0.06 | 8.22 (11.03) → 8.33 (11.19), +0.08 |
217
+ | 끌기(행 개수 변화) A→B | 8.36 (11.24) → 8.19 (11.04), −0.21 | 7.16 (8.50) → 7.10 (8.47), −0.08 | 7.06 (8.55) → 6.89 (8.52), −0.14 | 7.97 (10.35) → 7.94 (10.35), −0.08 |
218
+ | 회전(pose step만) | +0.02 | +0.07 | (생략) | (생략) |
219
+ | 문+램프 flag | +0.00 | −0.05 | +0.05 | −0.00 |
220
+
221
+ - 끌기: 4회 모두 중앙값이 0.08~0.21 ms 줄었다. A/A 폭(±0.06 ms)보다 크고, p95는 악화되지 않았다(0~−0.20 ms).
222
+ Node 추정(약 0.1 ms)과도 맞는다.
223
+ - 크기 교대: 중앙값이 −0.16, −0.13, −0.06, +0.08로 섞였고, p95는 2회에서 악화됐다(+0.29, +0.16 ms).
224
+ - 메모리(인스턴스마다 크기가 다른 distinct, 컨베이어 500대, 2회 같은 값): heap 67.7 → 74.6 MB(+6.9 MB), 은행 바이트
225
+ 28.56 → 34.22 MB(+20%). 생성 시간은 446~447 → 405~410 ms로 줄었다. 모두 dispose한 뒤 남는 양은 1.9 MB로 같았다.
226
+
227
+ 판정은 채택 보류다. 끌기에서는 개선이 일관되게 보였다. 그러나 크기 교대의 p95가 악화된 회차가 있고, 메모리가 은행
228
+ 기준 20% 늘어난다. 소스는 HEAD로 두었다. 남긴 것은 반복 크기 변경·생성·최종 dispose 뒤 회수 시험(HEAD에서 통과)과
229
+ `check-figure-ab.mjs`다. 후보 전용 시험 3개는 HEAD에서 실패하므로 커밋하지 않았다. 다시 시도할 때는 위 조건으로 같은
230
+ 시험을 먼저 넣는다.
231
+
232
+ 갱신 구간 동안만 부품을 잡는 후보(things-scene `docs/plans/figure-merged-part-renewal.md`)도 같은 날 시험했고 채택하지
233
+ 않았다. Node에서 새 크기 생성은 248 → 152 µs로 줄었지만, 한 페이지 교차 A/B의 frame 중앙값과 p95 차이가 같은 조건
234
+ A/A 폭 안에 있었다. 위 장기 보유 후보의 끌기 감소도 측정 변동이었을 가능성을 배제할 수 없다. 자세한 수치는 그 문서에 있다.
235
+
236
+ 측정 조건 기록: A/A(따로 띄우는 방식) 도중 `test/figure-merge.check.js`에 시험을 추가했다. 측정 스크립트는 실행마다
237
+ `src`를 rollup으로 한 번 묶으며, 시험 파일은 번들에 들어가지 않는다. 파일 감시나 자동 재빌드도 쓰지 않는다. 그래서
238
+ 측정 조건은 바뀌지 않았다고 판단한다.
239
+
240
+ ## 종합 판단
241
+
242
+ 이 패키지는 **이름 있는 부품의 치수·자세·반복·연결을 선언하고, 같은 정의를 저작·검사·씬에서 소비하는
243
+ 장비 조립체 커널**로는 충분한 구조를 갖췄다. 임의 CAD, 물리 시뮬레이터, 모든 JavaScript 형상을 대체하는
244
+ 범용 모델로 완결됐다고 말할 근거는 없다. 현재 기능 범위와 공개 API의 경계 조건도 구분해야 한다.
245
+
246
+ | 차원 | 판단 | 근거와 제한 |
247
+ | --------------- | --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
248
+ | 계산 의미 | 양호, 결과 소유권 보완 필요 | 단위·좌표계·단일 writer·순환 검사, 상태 전이 의존성, 순수 평가. 중첩 결과 수정이 다음 증분 평가에 유입됨 |
249
+ | 저작과 저장 | 양호, 검증 입구 통합 필요 | UI/AI 명령 공유, revision·원자 적용·undo. compile/serialize와 parse가 관절을 다르게 검증함 |
250
+ | 검사 완결성 | 명시적 제한 있음 | proven/sampled/defaults 구별은 좋음. 표본은 범위 보장이 아니며 검사 옵션 검증이 부족함 |
251
+ | 자원·안전 경계 | 부분적 | JSON 1 MiB/깊이 64, document 8,192노드, 반복 전개 한도. 긴 의존 사슬에서 일반 스택 오류 발생 |
252
+ | 성능 | 증분·자세 실행 구조 양호 | 인스턴스 상태 분리, 입력 영향 범위, 형상 공유 친화적 결과. 큰 출력 대비 소수 변경의 버퍼 할당은 개선 후보 |
253
+ | 확장성·유지보수 | 통제된 확장에 적합 | 버전 연산·형상 description·공유 명령. 연산 추가 시 여러 정책표를 함께 수정해야 하며 자동 기본값과 any가 실수를 가릴 수 있음 |
254
+
255
+ `npm test`: 타입 검사와 **696개 시험 통과**, 실패·skip·todo 없음(약 23.8초, 이번 환경).
256
+ 시험 통과는 아래 경계 사례까지 검증됐다는 뜻이 아니다. 이번에 코드 커버리지 수치나 브라우저 성능을 새로 측정하지 않았다.
257
+
258
+ ## 우선 고칠 사항: 재현된 문제
259
+
260
+ ### F1. 평가 결과의 중첩 객체가 evaluator 내부와 공유됨 — 높은 우선순위
261
+
262
+ > 해결됨(2026-10-04). 위 「해결 현황」 참고. 아래는 점검 당시의 기록이다.
263
+
264
+ 근거: `src/graph.ts`의 `hold`(1080행), `step` 반환, `src/graph-types.ts`의 가변 출력 타입.
265
+ `hold`는 values record와 geometry 배열만 복사한다. 그 안의 shape·pose·배열은 내부 slot과 공유된다.
266
+ 주석은 호출자의 수정이 다음 평가에 도달하지 않는다고 설명하지만 실제 보장은 최상위 컨테이너까지다.
267
+
268
+ 재현: 폭 5cm의 shape를 평가하고 반환된 `shape.dimensions['size.x']`를 999로 수정했다.
269
+ 같은 입력의 다음 증분 평가 결과는 999, 새 전체 평가 결과는 정상값 50이었다.
270
+ 기존 `incremental.test.ts`는 values의 새 속성 추가와 geometry push를 검사하며 중첩 수정을 검사하지 않는다.
271
+
272
+ 개선: 공유되는 결과 객체를 실제 불변으로 만들어 동일성·캐시 이점을 보존하거나, 공개 결과와 내부 버퍼를
273
+ 깊게 분리한다. 문서만 readonly로 바꾸는 것은 JavaScript 호출자의 변경을 막지 않는다.
274
+ 무조건 매 프레임 deep clone하기 전에 형상·자세의 공유 단위를 정하고 비용을 측정해야 한다.
275
+ 회귀 검증은 shape, pose, collection/member 중첩 수정과 인스턴스 간 격리를 포함한다.
276
+
277
+ ### F2. 관절 검증이 공개 입구마다 다름 — 높은 우선순위
278
+
279
+ > 해결됨(2026-10-04). 위 「해결 현황」 참고. 아래는 점검 당시의 기록이다.
280
+
281
+ 근거: `src/asset.ts`의 `compileFigureAsset`, `serializeFigureAsset`(208행), `parseFigureAsset`(212행).
282
+ 관절의 schema·정본/그래프 일치 검사는 parse에서만 `checkFigureJoints`로 실행한다.
283
+
284
+ 재현: hinged-door의 fixed joint에 금지된 `axis: 'Z'`를 추가했다.
285
+ compile과 serialize는 성공했지만, 직후 그 JSON을 parse하면 `JOINT_SCHEMA`로 실패했다.
286
+ 따라서 공개 serializer가 공개 parser로 읽을 수 없는 데이터를 만들어 낼 수 있다.
287
+
288
+ 개선: 공개 저장·컴파일 입구에서 동일한 자산 검증을 사용한다.
289
+ 관절 재작성 과정이 미완성 자산을 컴파일해야 한다면, 내부 전용 경로와 공개 검증을 분리해 재귀 호출을 피한다.
290
+ 합격 기준은 공개 serialize→parse의 일관성과 관절 레코드/그래프 drift의 동일한 거절이다.
291
+
292
+ ### F3. 허용 크기 안의 깊은 그래프에서 스택 초과 — 높은 우선순위
293
+
294
+ > 해결됨(2026-10-04). 위 「해결 현황」 참고. 아래는 점검 당시의 기록이다.
295
+
296
+ 근거: `src/graph.ts`의 재귀 `visit`(864행), `src/document.ts`의 노드 한도,
297
+ `src/shape-ops.test.ts`의 큰 그래프 시험.
298
+
299
+ 재현: 선언 순서를 의존 순서와 반대로 놓은 add 사슬을 document로 컴파일했다.
300
+ 2,000·5,000·8,000노드 모두 이번 Node 환경에서 `RangeError: Maximum call stack size exceeded`로 실패했다.
301
+ 각 JSON은 약 151·382·613KB로 1 MiB보다 작고 노드 수도 8,192 이하이다.
302
+ 현재 큰 그래프 시험은 모두 입력 `a`를 직접 읽는 넓은 그래프라 이 경계를 잡지 못한다.
303
+
304
+ 개선: 반복형 DFS 또는 Kahn 위상 정렬로 바꾸고 cycle 경로 진단을 유지한다.
305
+ 지원 깊이에 제한을 둔다면 명시적 `FigureContractError`로 거절한다.
306
+ 공개 `compileFigureGraph`는 document의 byte/node 경계를 거치지 않으므로, 저수준 API의 신뢰·예산 계약도 명시한다.
307
+ 반복마다 10,000개의 전개 한도가 있어도 전체 geometry 합계·slot·검사 실행 수가 자동으로 제한되지는 않는다.
308
+ 전체 평가 예산을 추가할지 별도 결정하고, 실제 메모리 상한은 측정한다.
309
+
310
+ ### F4. 검사 옵션을 검증하지 않아 잘못된 숫자가 계산 오류로 나타남 — 중간 우선순위
311
+
312
+ > 해결됨(2026-10-04). 위 「해결 현황」 참고. 아래는 점검 당시의 기록이다.
313
+
314
+ 근거: `src/gate.ts`의 `inspectFigureAsset`, 544~580행.
315
+ hinged-door에서 `sweepSteps: 1`은 0/0 때문에 `NON_FINITE`, 2.5는 입력 범위를 넘는 표본 때문에
316
+ `INPUT_RANGE`, 0은 sweep 없이 sampled 결과로 성공했다.
317
+ 이는 Figure 자체가 아니라 검사 설정의 문제인데 진단에서 구별되지 않는다.
318
+
319
+ 개선: sweepSteps를 2 이상 유한 정수로 검증하고 최대 표본 수를 제한한다.
320
+ maxCornerInputs도 유한 정수·지원 범위로 검증한다.
321
+ `1 << inputs.length`는 32비트 시프트이므로 옵션으로 31개 이상 전수 코너를 허용하면 산술도 부정확해진다.
322
+ 전수 표본 수를 사전에 계산해 예산을 확인하며, sampled/defaults 의미를 그대로 보존한다.
323
+
324
+ 재현 명령:
325
+
326
+ ```sh
327
+ node scripts/audit-completeness-2026-10-04.mjs
328
+ ```
329
+
330
+ 이 스크립트는 관찰을 출력하는 점검 도구다. 결함이 남아 있어야 통과하는 회귀 시험이 아니다.
331
+ 계약을 검증하는 회귀 시험은 「해결 현황」 표의 시험 파일에 있다.
332
+
333
+ ## 확장성: 좋은 기반과 제약
334
+
335
+ ### 현재 잘 갖춘 기반
336
+
337
+ - 단위와 frame을 타입으로 검사하고 상태 의존성을 전이적으로 전파한다. 관절 이름을 추측해 실행하지 않는다.
338
+ - 공유 컴파일 결과와 인스턴스별 evaluator 상태가 분리되어 있다. 증분·자세 경로는 기존 연산의 계산을 재사용하고 미지원 경로를 전체 평가로 돌린다.
339
+ - `shape-ops.ts`의 shape description이 측정·편집·feature를 연결한다. 새 형상을 추가할 때 소비자가 인자 위치를 제각각 해석할 필요가 줄었다.
340
+ - proposal의 command union 완전성 검사가 UI와 AI의 언어 차이를 줄인다. recipe는 데이터로 명령을 재사용한다.
341
+ - renderer 자체를 포함하지 않고 render plan을 제공한다. 등급과 렌더 묶음의 규칙을 공유할 수 있다.
342
+ - JSON duplicate key·비유한 값·getter·cycle 등 경계 방어가 있고, 검사 coverage가 표본과 증명을 구별한다.
343
+
344
+ ### 기능상의 의도적 제약
345
+
346
+ | 영역 | 현재 범위·제약 | 확장할 때 필요한 것 |
347
+ | ----------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
348
+ | 형상 | extrude/revolve/loft. loft는 단면→점 피라미드이며 임의 다단면 loft가 아님 | sweep·mesh import·CSG 등이 필요하면 형상·bounds/support·feature·편집·삼각형 계산·renderer 계약까지 함께 설계 |
349
+ | 연결 | 강체 frame과 DAG/tree 기반 관절 | 폐쇄 기구·연립 제약은 명시 관계 블록과 해 선택·불능 진단. 부모 둘로 우회하지 않기 |
350
+ | 로봇 | 정방향 자세, 관절 목록과 mimic | IK·충돌·속도/토크·동역학은 현재 커널 보장이 아님. 외부 solver와 입력/출력 계약부터 정의 |
351
+ | mimic | 여러 leader의 선형 합은 지원. follower를 다시 leader로 쓰는 연결은 저작 검증에서 거절 | 다단 mimic이 필요하면 단위·순환·범위·관절 목록 파생 규칙까지 검토 |
352
+ | 반복 | design에 의한 유한 topology, state에 의한 내부 운동 | state에 따른 생성/삭제는 현재 count 정책 밖. ordinal key가 물리적 객체 정체성을 보장하는지도 별도 결정 |
353
+ | 저작 | 만들어진 recipe는 보통 부품으로 삽입되고 recipe와의 연결은 사라짐 | 재사용 하위 조립체의 원본 수정 전파가 필요하면 template/instance/override 버전 계약 필요 |
354
+ | capability | Capacity/Holdable/Transferable/FlowNode의 닫힌 목록 | 새 행동은 커널 선언과 scene 구현을 함께 추가. 이름만 추가하면 기능 지원이 아님 |
355
+ | 배치 최적화 | 반복·정적·동일 carrier 묶음, 재질/투명성/visibility 등 조건 | 장면 간 배칭·LOD·GPU 표현은 별도 소비자 설계와 실측 필요 |
356
+
357
+ 이 제약들이 곧 결함은 아니다. 대표 장비에는 단순성과 결정성이 장점이다.
358
+ 다만 README의 “코드로 만들 수 있는 형상을 데이터로 만들기”는 목표이며 현재의 무제한 표현력 보장이 아니다.
359
+
360
+ ### 연산 확장 시 개선할 구조
361
+
362
+ `src/graph.ts` 675~707행은 새 연산에 state=`follows`, pure=true를 자동으로 준다.
363
+ 주석의 “정책 없는 연산은 거절”과 달리 catalog에 넣기만 하면 기본 정책이 생긴다.
364
+ 오늘의 순수 연산들에 대한 오답은 확인하지 않았지만, 향후 시간·외부 상태·topology 연산을 추가할 때
365
+ 누락된 정책이 안전하게 거절되지 않는 구조다.
366
+
367
+ 개선 방향은 연산 정의에 unit/frame signature, state policy, purity, pose form, extrema basis를 명시하고,
368
+ 불명확한 항목은 컴파일 거절 또는 검사 sampled로 처리하는 것이다.
369
+ 전체 목록을 고정하는 시험은 유지하되 정책 자체의 의미 검증을 대신하지 않도록 한다.
370
+ `Dynamic = any`는 현재 13개 파일에 있다. 먼저 연산 dispatch와 shape의 discriminated union부터 강화한다.
371
+ 한 번에 모듈을 모두 나누기보다 graph 검증·실행·pose 분석·연산 catalog의 책임 경계를 점진적으로 분리한다.
372
+
373
+ ## 검사·성능 숫자의 해석에서 보완할 것
374
+
375
+ 1. `linear.scalable`은 모든 입력에서의 수학적 증명이 아니다. 현재 `release-metrics.ts`는 authored size와
376
+ 일곱 resize 표본의 AABB 중심·extent 및 삼각형 수를 비교한다. bounds가 같아도 정점·feature가 같다는
377
+ 결론은 나오지 않는다. `probe`가 기록되는 것은 좋다. 범위·상태·검증 방법을 더 명확히 하고, 실제
378
+ 배칭의 허용 근거로 사용할 때에는 별도의 구조적 증명이나 정확한 geometry 동등성 조건을 요구한다.
379
+ 2. `atLargest`는 size 축의 max 조합에서 잰 비용이지, 모든 design/state 조합의 최대 비용 증명이 아니다.
380
+ 공개 pitch와 visibility·state-dependent geometry가 비용을 바꿀 수 있다. 측정점과 worst-case coverage를
381
+ 구분하고 단조성 증명 또는 보수적 예산 계산을 추가한다. 현재 등급도 기본 입력의 등급이다.
382
+ 3. `words.ts` 예산은 특정 장치에서 잰 draw/motion 값과 미측정 triangle 목표가 섞인다.
383
+ 등급을 일반 장치의 fps 보장으로 사용하지 말고, 장치·renderer·kernel·측정 시나리오를 가진 비용 profile로
384
+ 버전화하는 방향이 좋다. 최근 커널/rig 개선 뒤에도 기존 대당 계수가 유효한지는 재측정 대상이다.
385
+ 4. poseStep은 변경이 없어도 전체 bodyCount 크기의 Int32/Float64 버퍼를 할당한 뒤 slice한다.
386
+ 소수 변경에서도 쓰는 양보다 할당량이 커질 수 있다. 변경 없음의 빈 결과, 출력 크기 추정, 명시 수명의
387
+ 내부 버퍼를 검토하되 이전 결과 보존을 먼저 정하고 할당·GC·전체 프레임 개선을 측정한다.
388
+ 5. pose 결과의 `read`/`changed`는 내부 fast 슬롯·mark를 조회하는 closure다.
389
+ 이전 step을 보관한 뒤 다음 advance 후 조회할 때의 수명 계약을 문서화해야 한다.
390
+ 일반 snapshot과 frame 범위 조회 API를 혼동하지 않도록 한다.
391
+
392
+ 위 항목은 재현된 F1~F4와 구분한 설계·측정 개선 후보이다. 새 성능 수치나 실제 장면 결함으로 주장하지 않는다.
393
+
394
+ ## 권장 순서와 완료 조건
395
+
396
+ 1. F1 결과 소유권과 F2 공개 검증 일관성을 결정·수정하고 중첩 mutation 및 serialize/parse 회귀 시험을 추가한다.
397
+ 2. F3 반복형 그래프 컴파일과 F4 검사 옵션/실행 예산을 보완한다. 깊은·넓은·순환 그래프와 설정 경계를 확인한다.
398
+ 3. 지원 기능·의도적 제한·증명/표본의 구별을 정본 문서에 모으고, 재현 script와 현행 상태 링크를 유지한다.
399
+ 4. 새 연산의 정책을 필수화하고 catalog/type를 단계적으로 정리한다. 기존 자산의 의미와 fallback을 보존한다.
400
+ 5. 비용 profile과 pose 할당을 실제 장면에서 검토한다. 정확성이 유지되고 전체 프레임이 개선될 때 채택한다.
401
+ 6. IK·폐쇄 기구·새 형상·하위 template 등은 실제 요구 사례 순서로 별도 설계한다. 기능 수를 늘리는 것 자체를
402
+ 완결성 지표로 삼지 않는다.
403
+
404
+ 이번 완료 범위는 저장소 점검·문서화·재현 근거 마련이다. 런타임 결함 수정, 소비자 전체 검증,
405
+ 배포·발행은 후속 작업이며 수행하지 않았다.
@@ -1,4 +1,4 @@
1
- # Figure Model V3 핵심 의미론 기준안 — r3
1
+ # Figure Model 핵심 의미론 기준안 — r3
2
2
 
3
3
  > 현재 구현·검증·제품 연결 상태는 [구현·검증 현황](status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
@@ -271,7 +271,7 @@ AI의 자유 텍스트가 geometry나 외부 바인딩을 직접 바꾸지 않
271
271
  정점 수, draw call, resolve/compile 시간, 캐시 적중과 메모리를 실제 규모에서 측정한다.
272
272
  수치 없는 '고성능 보장'은 하지 않는다.
273
273
 
274
- 제품은 V3 단일 실행 체계를 쓴다. 기존 자산은 V3 재작성으로 교체한다.
274
+ 제품은 단일 실행 체계를 쓴다. 기존 자산은 지금 형식으로 다시 작성해 교체한다.
275
275
  복구용 원본 보관은 데이터 교체의 안전 절차이지 모델 설계 제약이 아니다.
276
276
  현재 좌표를 고정한 변환과 반응형 의도를 복원한 재작성을 같은 성공으로 보고하지 않는다.
277
277
 
package/docs/cutover.md CHANGED
@@ -1,10 +1,10 @@
1
- # V3 단일 체계 전환 — 작업 기준
1
+ # 단일 체계 전환 — 작업 기준
2
2
 
3
3
  > 현재 구현·검증·제품 연결 상태는 [구현·검증 현황](status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
5
- 사용자 결정: 제품은 V3 하나를 쓴다. V3의 단순성·일관성·저작성·품질을 우선하며, 버전별 편집 경로는 두지 않는다.
5
+ 사용자 결정: 제품은 한 가지 형식만 쓴다. 그 형식의 단순성·일관성·저작성·품질을 우선하며, 버전별 편집 경로는 두지 않는다.
6
6
 
7
- 제품 모델러·AI·저장·렌더러는 V3 하나를 기준으로 한다. 기존 자산은 V3로 재작성한다. 기존 데이터의 파괴적 교체는 대상 확인과 복구본 확보 후 별도로 수행한다.
7
+ 제품 모델러·AI·저장·렌더러는 이 형식 하나를 기준으로 한다. 기존 자산은 지금 형식으로 재작성한다. 기존 데이터의 파괴적 교체는 대상 확인과 복구본 확보 후 별도로 수행한다.
8
8
 
9
9
  V2와 V1은 2026-09-26 코드와 명세에서 제거했다. ADR-0087의 전환은 제거로 마쳤다.
10
10
 
@@ -26,4 +26,4 @@ V2와 V1은 2026-09-26 코드와 명세에서 제거했다. ADR-0087의 전환
26
26
 
27
27
  제품의 버튼 연결, 다른 primitive 생성, 관계 편집, 실제 DB 저장·렌더러 전환은 아직 완료하지 않았다. 위치와 크기를 바꿔 바닥을 유지하려면 두 입력을 함께 편집해야 한다. 이번 상자 생성은 조립 관계를 자동 추론하지 않는다.
28
28
 
29
- 이제 다음 제품 작업은 V3 생성·편집 명령을 실제 모델러의 사용자 동작에 연결하는 것이다. 호환 분기 추가는 하지 않는다. 패키지 설치·배포는 소스 구현과 구별해 확인한다.
29
+ 이제 다음 제품 작업은 Figure 생성·편집 명령을 실제 모델러의 사용자 동작에 연결하는 것이다. 호환 분기 추가는 하지 않는다. 패키지 설치·배포는 소스 구현과 구별해 확인한다.
@@ -1,10 +1,10 @@
1
- # V3 그래프·capability 직렬화 계약 — 실험판
1
+ # Figure 그래프·capability 직렬화 계약 — 실험판
2
2
 
3
3
  > 현재 구현·검증·제품 연결 상태는 [구현·검증 현황](status.md)을 따른다. 본문의 단계별 연구/검증 기록은 제품 전체 완료 선언이 아니다.
4
4
 
5
5
  ## 범위와 상태
6
6
 
7
- 실험 그래프와 capability 선언을 저장하고 재로드해 의미 검증하는 계약을 구현했다. **전체 Figure V3 제품 파일 포맷 확정이 아니다.** 현재 전체 모델 예제의 재질·분할 수·외부 capability 메타데이터 일부는 어댑터가 공급한다. 그 데이터까지 이 포맷이 저장한다고 주장하지 않는다. 제품 DB·서버·UI는 변경하지 않았다.
7
+ 실험 그래프와 capability 선언을 저장하고 재로드해 의미 검증하는 계약을 구현했다. **전체 Figure 제품 파일 포맷 확정이 아니다.** 현재 전체 모델 예제의 재질·분할 수·외부 capability 메타데이터 일부는 어댑터가 공급한다. 그 데이터까지 이 포맷이 저장한다고 주장하지 않는다. 제품 DB·서버·UI는 변경하지 않았다.
8
8
 
9
9
  후속 [자산 저장 envelope](asset-persistence.md)는 이 document를 감싸 설계 입력·동작 기본값·재질·팔레트·분할 품질을 함께 저장한다. document 단독 포맷과 자산 envelope의 지원 범위를 혼동하지 않는다.
10
10
 
@@ -32,7 +32,7 @@ JSON Schema는 필드 구조·자료형·기본 enum·버전을 검사한다. op
32
32
 
33
33
  ## 손실·오류 정책
34
34
 
35
- - 알 수 없는 문서/계약 버전과 추가 필드를 거부한다. 현재 capability V3는 transform 또는 표면 참조 pose를 허용한다. V1/V2를 추측 변환하지 않는다.
35
+ - 알 수 없는 문서/계약 버전과 추가 필드를 거부한다. 현재 capability 선언은 transform 또는 표면 참조 pose를 허용한다. V1/V2를 추측 변환하지 않는다.
36
36
  - JSON 키 중복은 마지막 값으로 덮지 않고 거부한다. Unicode escape로 같은 키가 표현된 경우도 포함한다.
37
37
  - 잘린 JSON, 비유한 수, undefined, 함수, Date, 순환, sparse array, accessor·숨겨진 속성 등 JSON으로 손실되는 입력을 거부한다.
38
38
  - 실험 자원 제한: 1MiB UTF-8, 객체 깊이64, 그래프 node 8192(`FIGURE_DOCUMENT_NODE_LIMIT`, 2026-09-29 판정 — shape-contract Q10: 충분히 크게. 실제로는 1MiB 가 먼저 걸린다).
@@ -4,7 +4,7 @@
4
4
 
5
5
  > **2026-09-26:** `portal-profile@1`(좌표 여덟)은 `portal-profile@2`로 바뀌었다. 입력은 폭 · 높이 · 깊이 · 기둥 두께 · 인방 두께의 다섯이고, 좌우로 가운데, 바닥에 서며, 좌표 여덟은 이 값에서 나온다. 앞 셋이 상자와 같아 크기를 따를 수 있다. 아래는 @1 때의 기록이다.
6
6
 
7
- 상태: 독립 V3 실험. 제품/DB/원본 시드 수정 없음.
7
+ 상태: 독립 Figure 실험. 제품/DB/원본 시드 수정 없음.
8
8
  `operato-application/packages/operato-figure/server/seed/figure-samples.json`의
9
9
  DRY_ROOM portal-in/out과 lamp-in/out을 읽었다. 서버에서 편집된 revision은 검사하지 않았다.
10
10
  shell/readout을 포함한 드라이룸 전체 변환도 아니다.