@hatiolab/figure-model 0.1.34 → 0.1.35
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.
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/v3-asset-types.d.ts +2 -0
- package/dist/v3-asset-types.d.ts.map +1 -1
- package/dist/v3-asset.d.ts.map +1 -1
- package/dist/v3-asset.js +4 -1
- package/dist/v3-asset.js.map +1 -1
- package/dist/v3-capabilities.js +1 -0
- package/dist/v3-capabilities.js.map +1 -1
- package/dist/v3-from-v2.d.ts +57 -3
- package/dist/v3-from-v2.d.ts.map +1 -1
- package/dist/v3-from-v2.js +276 -91
- package/dist/v3-from-v2.js.map +1 -1
- package/dist/v3-gate.d.ts +53 -4
- package/dist/v3-gate.d.ts.map +1 -1
- package/dist/v3-gate.js +175 -21
- package/dist/v3-gate.js.map +1 -1
- package/dist/v3-graph-types.d.ts +6 -1
- package/dist/v3-graph-types.d.ts.map +1 -1
- package/dist/v3-graph.d.ts +10 -0
- package/dist/v3-graph.d.ts.map +1 -1
- package/dist/v3-graph.js +77 -16
- package/dist/v3-graph.js.map +1 -1
- package/dist/v3-kernel-version.d.ts +8 -0
- package/dist/v3-kernel-version.d.ts.map +1 -0
- package/dist/v3-kernel-version.js +11 -0
- package/dist/v3-kernel-version.js.map +1 -0
- package/dist/v3-mesh-compare.d.ts +27 -0
- package/dist/v3-mesh-compare.d.ts.map +1 -0
- package/dist/v3-mesh-compare.js +198 -0
- package/dist/v3-mesh-compare.js.map +1 -0
- package/docs/v3-operator-contracts.md +3 -0
- package/docs/v3-shape-dimension-contract.md +146 -0
- package/package.json +2 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# V3 shape-dimension contract — per-axis radii, elliptic sections, and dimensions that follow state
|
|
2
|
+
|
|
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
|
|
6
|
+
compiler and printed as `dependence` beside `outputTypes`; every operator carries a `state` policy (`follows` or
|
|
7
|
+
`refuses`; an operator without one fails with `STATE_POLICY`); `occupancy.extent` may not depend on state
|
|
8
|
+
(`OCCUPANCY_STATE`); `inspectV3Asset` covers the state range and reports `proven` (corners; no state-dependent
|
|
9
|
+
rotation), `sampled` (a rotation depends on state; corners plus per-input sweeps, step stated, not a proof) or
|
|
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
|
|
13
|
+
exceptions bolted on for the three assets that needed them.
|
|
14
|
+
|
|
15
|
+
## What a person sees
|
|
16
|
+
|
|
17
|
+
An author opens the electrode roll cradle. A "fed" value sits at 0 %. As they drag it toward 100 %, the roll on the
|
|
18
|
+
cradle gets thinner: its radius shrinks, its length does not. The edge plates that ride on the roll shrink with it and
|
|
19
|
+
stay on its surface. Nothing else moves. At 0 % the roll is exactly the drawn roll.
|
|
20
|
+
|
|
21
|
+
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.
|
|
24
|
+
|
|
25
|
+
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.
|
|
27
|
+
|
|
28
|
+
## 1. Every round thing has per-axis dimensions
|
|
29
|
+
|
|
30
|
+
A section is round only when its two cross dimensions are equal. The providers write both:
|
|
31
|
+
|
|
32
|
+
| operator | evaluated provider | dimensions | round when | status |
|
|
33
|
+
|---|---|---|---|---|
|
|
34
|
+
| `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) |
|
|
36
|
+
| `sphere-shape@1` | `sphere` | `radiusX`, `radiusY`, `radiusZ` (already so) | all equal | implemented |
|
|
37
|
+
| `rounded-box@2` | `roundedBoxXZ` | `width`, `height`, `depth`, `roundX`, `roundZ` | `roundX = roundZ` | implemented 2026-09-22 |
|
|
38
|
+
| `hollow-box@2` | `hollowBoxXZ` | as `hollow-box@1` with `roundX`, `roundZ` in place of `round` | same | implemented 2026-09-22 |
|
|
39
|
+
|
|
40
|
+
The evaluated provider name is distinct from the `@1` one so a renderer can never mistake per-axis dimensions for a
|
|
41
|
+
single radius. Features on `ellipticCylinder` are `start` and `end` only; `radialBottom` has no single meaning on an
|
|
42
|
+
ellipse.
|
|
43
|
+
|
|
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.
|
|
46
|
+
- **The renderer draws what it is given.** An elliptic section is an ellipse; an elliptic corner is the quadratic
|
|
47
|
+
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.
|
|
51
|
+
- 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.
|
|
54
|
+
|
|
55
|
+
### What `keepRound` becomes
|
|
56
|
+
|
|
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:
|
|
59
|
+
|
|
60
|
+
```
|
|
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)
|
|
63
|
+
```
|
|
64
|
+
|
|
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.
|
|
70
|
+
|
|
71
|
+
## 2. A dimension may depend on state
|
|
72
|
+
|
|
73
|
+
A state input (`role: 'state'`) may be an argument of a shape or length node. A consumed roll's radius is
|
|
74
|
+
`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)`.
|
|
76
|
+
|
|
77
|
+
### Dependence is tracked in the type, apart from unit and frame
|
|
78
|
+
|
|
79
|
+
Every value carries, next to its unit or frame, a **dependence set**: which state inputs its value can change with.
|
|
80
|
+
Inputs of role `state` start with `{self}`; constants and design inputs with `∅`; every operator's outputs carry the
|
|
81
|
+
union of its arguments' sets, unless the operator's contract says otherwise. This is the existing `stateful`
|
|
82
|
+
tracking in `compileV3Graph`, made a first-class part of the type and printed in `outputTypes`.
|
|
83
|
+
|
|
84
|
+
### Every consuming operator declares its policy
|
|
85
|
+
|
|
86
|
+
An operator that receives a state-dependent argument does one of three things, **written in its contract**, and a
|
|
87
|
+
new operator that says nothing fails to compile the moment such an argument reaches it. There is no list of banned
|
|
88
|
+
operators to keep in sync.
|
|
89
|
+
|
|
90
|
+
| policy | meaning | operators |
|
|
91
|
+
|---|---|---|
|
|
92
|
+
| `follows` | the output depends on state; fine | shape providers, `rigid`, `compose`, `attach`, `axis-turn`, `axis-slide`, `add`, `mul`, `div`, `geomean`, `feature`, `place`, `member` |
|
|
93
|
+
| `refuses` | a state-dependent argument is a compile error | `fit-pitch@1`, `fixed-count@1` (count and pitch: structure and cost stay design-only; this is today's `TOPOLOGY_STATE`), the three lengths named by `occupancy.extent` |
|
|
94
|
+
| `bounded` | allowed, and the contract states the resource limit that holds over the whole state range | none today; the slot for a future layout that may follow state |
|
|
95
|
+
|
|
96
|
+
So: shape, attachment position and pose may follow state (a plate riding on the thinning roll follows it); the
|
|
97
|
+
declared occupancy is a state-independent contract; structure- and cost-changing operations say so themselves.
|
|
98
|
+
|
|
99
|
+
## 3. The occupancy holds over the whole valid state range
|
|
100
|
+
|
|
101
|
+
`occupancy` is the contract a board's placement relies on (ADR-0087 decision 7). A volume that is true at the
|
|
102
|
+
default state and false at another is not that contract. So `inspectV3Asset` answers for the **whole** valid state
|
|
103
|
+
range, and says how it knows:
|
|
104
|
+
|
|
105
|
+
| how the geometry depends on state | how the check is done | what the report says |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| no state dependence | one evaluation at the defaults | `proven` |
|
|
108
|
+
| every state-dependent world coordinate is affine or monotone in each state input (shape dimensions from `add`/`mul`/`geomean` of a single state input; slides along an axis) | evaluate at every corner of the state box (each input at min and max); the extremes are at the corners | `proven`, listing the corners |
|
|
109
|
+
| a rotation or other non-linear composition is in the chain (`axis-turn` under a state input) | a conservative bound: the swept envelope of the turning subtree over the joint's range, or an extremum search on the offending coordinate with a stated step | `bounded`, with the method |
|
|
110
|
+
| neither is available | sample the range with a stated step | `sampled`, with the range and the samples; **never** worded as a whole-range guarantee |
|
|
111
|
+
|
|
112
|
+
- A violation found anywhere in the range is a violation. The gate never widens the occupancy to make it go away.
|
|
113
|
+
The author either declares a volume that covers the valid range, or narrows the allowed state range.
|
|
114
|
+
- The endpoint-only shortcut is **wrong** for a rotating long part: inside at both end angles, out in the middle.
|
|
115
|
+
That case takes the `bounded` row, not the `proven` one.
|
|
116
|
+
|
|
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
|
|
131
|
+
|
|
132
|
+
- The four providers' `@2` forms compile, evaluate, and their `@1` forms are unchanged (the operator-list guard
|
|
133
|
+
gains the four names).
|
|
134
|
+
- Dependence appears in `outputTypes`; a synthetic new operator with no policy fails to compile when handed a
|
|
135
|
+
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
|
+
- An occupancy check on the roll cradle reports `proven` over the `fed` range, and a synthetic rotating long part
|
|
138
|
+
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
|
+
|
|
142
|
+
## Not in this contract
|
|
143
|
+
|
|
144
|
+
- Theme mapping and the colour cache (renderer work; ADR-0087 stage review ruling 4).
|
|
145
|
+
- Shape deformation beyond dimensions: bending, twisting, free-form profiles that change with state. Those would be
|
|
146
|
+
new providers with their own contracts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hatiolab/figure-model",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.35",
|
|
4
4
|
"description": "Figure 저작 결과의 정본 형식과 검증 — 재질별로 병합되고 이름이 붙은 부품 그래프. 3D 로 저작한 형상 하나가 씬 컴포넌트로 서는 데 필요한 것을 담는다.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"figure",
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"access": "public"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
|
+
"version": "node scripts/sync-kernel-version.mjs && git add src/v3-kernel-version.ts",
|
|
65
66
|
"build": "tsc -p tsconfig.build.json",
|
|
66
67
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
67
68
|
"test": "npm run check && node --test src/*.test.ts",
|