# 온도계(thermometer) 설계서 — 구현자용

설계·검증: Claude (coordinator). 구현: 이 문서를 받은 worker. 정한 것은 그대로 따르고, 바꿔야 하면 바꾸지 말고 `orca orchestration ask`로 묻는다. 숫자를 조정했으면 worker_done에 적는다.

일꾼 규칙(요약): 설계를 새로 하지 말고 이 문서를 코드로 옮긴다. 막히면 preamble의 `ask`. 스크린샷과 확대 크롭을 직접 보고 확인한다. 브라우저 시험은 `.agents/skills/anatomy/scripts/drive.mjs`. 관은 `kit/tube.mjs`, 둥근 부품은 `kit/lathe.mjs`. 진행 보고는 첫 preview 성공 때 한 번, 끝나기 전에 `check`, `worker_done`의 바꾼 파일은 `git status`로. 이 저장소 밖의 파일(특히 `~/.claude/`, `~/.codex/`)은 읽지 않는다. **이 페이지는 베리언트 구조다**: 너의 파일은 `pages/thermometer/variants/<VID>/` 아래에 둔다. `<VID>`는 spec이 알려 준다.

## 0. 먼저 읽기
`docs/concept.md`, `.agents/skills/isowiki-entry/SKILL.md`, `docs/knowhow.md`("그림 안 라벨", "휴대폰", "판과 모서리 문구", "작은 핵심부"), `docs/provenance.md`의 "베리언트" 절, 코드 본보기 `pages/ball-bearing/variants/deepseek-pi/figure/{model.mjs,scene.mjs,index.mjs,live.js}`(관과 둥근 부품, 버튼, 투어가 모두 있다), kit 참조 `.agents/skills/anatomy/references/kit.md`.

## 1. 한 문장
같은 판에 붙은 보통 알코올 온도계와 최고 최저 온도계가 같은 기온을 잰다. 따뜻해지면 둘 다 기둥이 오르고, 최고 최저 온도계는 수은이 쇠 눈금 표시기를 밀어 올려 놓고 가서, 다시 식어도 그날의 최고 온도와 최저 온도가 남는다. 자석으로 표시기를 수은 면까지 끌어내리면 새로 잴 준비가 된다.

독자가 하는 일: "따뜻하게"(30 °C) / "차갑게"(−10 °C) 버튼으로 기온을 바꾸고, "자석으로 되돌리기"로 표시기를 내린다. 손대지 않으면 투어가 하루(낮 → 밤 → 아침 → 되돌리기)를 돈다.

## 2. 파일
- `pages/thermometer/index.md`(공유, 아래 그대로), `pages/thermometer/variants/<VID>/index.md`(베리언트, 아래 그대로로 시작), `pages/thermometer/variants/<VID>/figure/{model.mjs, scene.mjs, live.js, index.mjs}`(ball-bearing과 같은 구성).
- `build.mjs`: `PARTS` 2부 items에서 `["engine", "4행정 엔진"]` 뒤에 `["thermometer", "온도계"]`, `LABELS` 끝에 `thermometer:"온도계"`. 이 두 곳만.
- `generator`와 `revisions`는 **고치지 않는다**. 병합할 때 조정자가 dispatch별로 채운다.

공유 `pages/thermometer/index.md` (그대로, `<VID>`만 바꿔서):
```
---
title: 온도계
en: Thermometer
part: 2
related: [thermodynamics, pressure, refrigerator]
default: <VID>
revisions:
  - date: 2026-10-15
    target: [article]
    model: claude-opus-5-5
    modelId: claude-opus-5-5
    harness: claude-code 2.1.299
    session: c0557bfd-7c20-45bf-a3c9-ce3c1f39ab97
    note: "공유 정보(제목, 부, 연결 문서). 설계서에서 그대로 옮김."
---
```

베리언트 `pages/thermometer/variants/<VID>/index.md` 시작 판 (본문과 description·lead·card는 4단계에서 쓴다):
```
---
description: 작업 중
lead: 작업 중
card: 데우면 늘고 식히면 준다
generator:
  model: 작업 중
  modelId: null
  harness: 작업 중
  effort: [high]
  prompt: []
  note: "작업 중. 병합 때 조정자가 채운다."
revisions:
  - date: 2026-10-15
    target: [figure]
    model: 작업 중
    modelId: null
    note: "작업 중. 병합 때 조정자가 채운다."
---

(4단계에서 쓴다.)
```

## 3. 모형 (model.mjs) — 길이 mm, 온도 °C, 시간 s
```js
// A spirit thermometer and a Six's maximum–minimum thermometer on one board, at the same air temperature.
// Both columns move RISE mm per kelvin. Expansion coefficients are taken as constant (a model; real ones vary a little).
export const BETA = { ethanol: 1.09e-3, mercury: 1.8e-4, glass: 2.7e-5 }; // volume expansion near 20 °C, 1/K
export const BORE = 0.5;                                    // real bore diameter, mm (drawn about 5x wider)
export const AREA = Math.PI * (BORE / 2) ** 2;              // 0.196 mm^2
export const RISE = 2.0;                                    // mm of column per kelvin
export const SENSE = (RISE * AREA) / (BETA.ethanol - BETA.glass); // 369 mm^3 of alcohol in each sensing bulb
export const T_LOW = -30, T_HIGH = 50;                      // scale range
export const Z_LOW = 70;                                    // height of -30 °C on the plain and the maximum scale
export const SUM = 300;                                     // the two mercury surfaces of the U always add up to this
export const zPlain = (t) => Z_LOW + RISE * (t - T_LOW);    // top of the red column
export const zMax = (t) => Z_LOW + RISE * (t - T_LOW);      // mercury surface, right (maximum) arm
export const zMin = (t) => SUM - zMax(t);                   // mercury surface, left (minimum) arm
export const readMax = (z) => T_LOW + (z - Z_LOW) / RISE;   // a height on the maximum scale -> °C
export const readMin = (z) => T_LOW + (SUM - z - Z_LOW) / RISE; // a height on the minimum scale (upside down) -> °C
export const WARM = 30, COLD = -10;                         // what the buttons set the air to
export const TAU = 2.0;                                     // seconds for the bulbs to follow the air (figure time)
export const INDEX = 18;                                    // steel index length, mm
// state: air (°C), t (thermometer °C), hi and lo (°C the indices show; hi >= t, lo <= t)
export const START = { air: 15, t: 15, hi: 24, lo: 6 };
```
설계자 검산: SENSE 369 mm³(0.37 mL). START에서 오른쪽 수은 면 z 160, 왼쪽 140, 최고 표시기 아래 끝 z 178(=zMax(24)), 최저 표시기 아래 끝 z 158(=zMin(6)), 빨간 기둥 끝 z 160. 수은 기둥의 관 속 길이는 늘 같다(왼쪽 z + 오른쪽 z = 300).
- 표시기 규칙: hi = max(hi, t), lo = min(lo, t). 그리는 위치: 최고 표시기 아래 끝 z = zMax(hi), 최저 표시기 아래 끝 z = zMin(lo). 되돌리기: hi = lo = t.
- 오른쪽 통의 알코올 면은 고정(z 262). 실제로는 오른쪽 관에서 밀려 올라온 알코올만큼 오르지만 관이 가늘어 0.1 mm도 안 된다.

## 4. 세계와 카메라
- 1 단위 = 1 mm, x 오른쪽, y 앞(관찰자 쪽), z 위. 판은 x–z 평면에 서 있고 앞면이 y = 0이다.
- 카드 600 × 460. `k.fitProjection(points, 600, 460, { pad: 26, azimuth: 72, elevation: 16 })`. points: 받침판 꼭짓점(`k.boxCorners({x:-150,y:-40,w:300,d:100,r:8}, -17, 0)`), 판 꼭짓점(`k.boxCorners({x:-95,y:-14,w:195,d:14,r:4}, 0, 300)`). 설계자가 셈한 값: scale 0.94, 판이 화면에서 폭 약 220 px · 높이 약 370 px, z 1 mm ≈ 1.15 px. 판 양옆 약 190 px씩은 라벨 자리다.
- 관의 중심선은 y = 6(판 앞면에서 6 mm 앞).

### 고정 부품
| 부품 | 형상 |
|---|---|
| 받침판 | `basePlate(P, {x:-150,y:-40,w:300,d:100,r:8}, { label, strip })` |
| 판 | 흰 칠한 나무판: 슬래브 x −95…100, y −14…0, z 0…300, 모서리 r 4. 앞면이 화면을 본다. 받침판 위에 선다(뒤에 작은 버팀다리 하나: x 0, y −14…−40 삼각 슬래브, 두께 10) |
| 관 고정 클립 | 관마다 3개: 작은 금속 띠(폭 6, 높이 4)가 관을 판에 붙인다. 보통 온도계 z 60·150·240, 최고 최저 온도계 왼쪽·오른쪽 관 각각 z 60·150·235 |
| 눈금 (판 앞면 y = 0에 그린 선과 숫자) | 5 °C마다 짧은 눈금(4 mm), 10 °C마다 긴 눈금(7 mm)과 숫자. 숫자 크기 판 글씨 8 mm 상당. **보통 온도계**: 관 오른쪽(x −48…−30), z = zPlain(t), 위에 "°C". **최고 쪽**(오른쪽 관): 관 오른쪽(x 72…92), z = zMax(t), 위에 "최고". **최저 쪽**(왼쪽 관): 관 왼쪽(x −12…8), z = zMin(t) — **−30이 위(z 230), 50이 아래(z 70)**, 위에 "최저" |

### 보통 온도계 (중심선 x = −55)
- 구(bulb): `G.lathe`(z축 프레임), z 20…52, 최대 반지름 6(z 34에서), 아래는 둥글게, 위는 관 반지름으로 좁아진다. 붉게 물들인 알코올로 가득.
- 관: 유리 바깥 r 3, z 52…250. 안의 붉은 기둥 r 1.3, z 52…zPlain(t). 기둥 위로는 빈 관(유리만).
- 위 끝 팽창실: z 250…262, r 3.5의 작은 통(빈 공간).

### 최고 최저 온도계 (U자 관)
- 왼쪽 관(최저 쪽) 중심선 x = 15, 오른쪽 관(최고 쪽) x = 65. 굽은 곳: 중심 (40, 6, 45), 반지름 25, 바닥 z 20. 곧은 관은 z 45…245.
- 유리: 관 바깥 r 3(`tubePieces` 한 줄: 왼쪽 위 → 아래 → 굽은 곳 → 오른쪽 위).
- 왼쪽 위 **감온 통**: z 245…292, 최대 반지름 7.5, 방추형(위아래가 좁아진다). 알코올로 **가득** 찬다(엷은 파랑).
- 오른쪽 위 **통**: z 245…282, 반지름 7, 둥근 통. 아래쪽 z 245…262는 알코올, 그 위 z 262…282는 공기(빈 유리 — 안쪽을 판 색보다 조금 밝게).
- 관 속 액체(r 1.3): 수은은 왼쪽 면 zMin(t)부터 아래로 굽은 곳을 지나 오른쪽 면 zMax(t)까지(은색, 위쪽에 밝은 줄 하나). 수은 위 양쪽 관은 통까지 알코올(엷은 파랑).
- **눈금 표시기** 2개: 쇠 철사를 넣은 작은 유리 막대, 관 안 r 1.0, 길이 INDEX(18). 짙은 쇠색, 양 끝이 조금 둥글고 가늘다. 위쪽 1/3에 머리카락 같은 용수철 한 가닥(관 벽 쪽으로 비스듬한 짧은 선). 최고 표시기: 오른쪽 관, 아래 끝 z = zMax(hi). 최저 표시기: 왼쪽 관, 아래 끝 z = zMin(lo).
- 표시기는 작은 핵심부다: 화면에서 표시기가 수은 기둥·알코올과 확실히 구별되게 짙게 그린다(크기는 키우지 않는다 — 실제 길이 약 19 mm와 같다).

### 자석 (움직이는 부품)
- 말굽자석: 빨간 U자(바깥 폭 26, 높이 30, 두께 8, 팔 폭 8)와 두 끝의 은색 극(높이 6). 쉬는 자리: 받침판 위 앞 오른쪽, 바닥이 판 위(z 0), 중심 (80, 30, *), 극이 왼쪽을 본다(눕혀 둠).
- 2단계에서 되돌릴 때 관 앞(y 16)으로 와서 표시기를 끌어내린다(§7).

## 5. 그리는 순서 (뒤 → 앞)
1. 받침판, 버팀다리
2. 판(슬래브), 판 앞면의 눈금·숫자·"°C"/"최고"/"최저"
3. 유리 관·통의 몸(엷은 채움) — 보통 온도계, U자 관
4. 관 속 액체: 알코올(엷은 파랑), 붉은 기둥, 수은
5. 눈금 표시기
6. 유리 테두리와 밝은 줄(유리가 액체 위로 보이게)
7. 클립
8. 자석
9. 라벨

## 6. 판(plate)
- title "온도계". hint "버튼으로 기온 바꾸기".
- readout: "지금 15 °C · 최고 24 · 최저 6"(정수, 25자 안쪽).
- keys: [{ mark: "lit", label: "눈금 표시기: 수은이 밀어 올린 자리에 남는다" }] — 표시기 둘을 lit.
- caption(모형값): 알코올 감온부 0.37 mL, 관 안지름 0.5 mm(그림은 약 5배 굵게), 1 °C에 2 mm, 팽창 계수는 일정하다고 둠, 온도계가 기온을 따라가는 빠르기는 실제보다 빠르게(2 s).
- controls(`lib/controls.mjs`): 그룹 "기온" 버튼 "따뜻하게"/"차갑게", 그룹 "눈금 표시기" 버튼 "자석으로 되돌리기".
- 그림 안 라벨(knowhow "그림 안 라벨"): 알코올(감온 통) · 공기(오른쪽 통 윗부분) · 수은(굽은 곳) · 눈금 표시기(최고 쪽) · 자석 · 붉게 물들인 알코올(보통 온도계 구). 왼쪽 라벨은 판 왼쪽 여백, 오른쪽 라벨은 판 오른쪽 여백으로 끌어낸다.

## 7. 움직임(2단계; 1단계는 START 한 장면)
- 매 프레임: t += (air − t)·(1 − e^(−dt/TAU)); hi = max(hi, t); lo = min(lo, t). 기둥·수은·표시기를 다시 그린다.
- "따뜻하게" air = WARM, "차갑게" air = COLD. aria-pressed는 지금 air와 맞춘다.
- "자석으로 되돌리기": 자석이 쉬는 자리에서 떠올라 오른쪽 관 앞(y 16)의 최고 표시기 높이로 0.6 s, 표시기를 달고 수은 면까지 내려 감 0.6 s(hi → t), 왼쪽 관 최저 표시기로 옮겨 0.5 s, 수은 면까지 내려 감 0.6 s(lo → t), 쉬는 자리로 0.6 s. 그동안 기온 버튼은 동작한다(되돌리는 중에도 t가 바뀌면 표시기는 그 순간의 수은 면까지 간다).
- 투어(손대지 않을 때): 차갑게 → 6 s → 따뜻하게 → 6 s → (air 15로) 6 s → 되돌리기 → 3 s → 반복. 조작하면 멈추고 8 s 뒤 처음부터. 화면 밖이면 멈춤, reduced motion이면 투어 없이 즉시 상태.
- 휴대폰: 끌기가 없으니 무대는 `touch-action: pan-y` 그대로, 그립 없음.

## 8. 본문(4단계)
절: 왜 필요한가 / 어떻게 움직이나 — 보통 온도계 / 최고 최저 온도계 / 눈금 표시기와 자석 / 왜 이렇게 생겼나 / 이 그림에 대하여. 사실은 §9에서만. 그림의 부품 이름(감온 통, 수은, 눈금 표시기, 자석)으로 쓴다. 역사·연도·종류 표·식 블록·출처 목록은 쓰지 않는다.

## 9. 사실표 (출처는 싣지 않는다)
1. 물질은 데우면 부피가 늘고 식히면 준다. 액체 온도계는 구(bulb)의 액체가 늘어난 부피를 가는 관 속 기둥의 길이로 바꿔 보인다. 관이 가늘수록 같은 부피 변화가 더 긴 기둥이 된다.
2. 부피 팽창 계수(20 °C 부근): 에탄올 약 0.0011 /K, 수은 약 0.00018 /K(에탄올이 약 6배), 보통 유리 약 0.000027 /K.
3. 유리 구도 함께 늘어나므로 기둥은 액체가 늘어난 만큼에서 유리가 늘어난 만큼을 뺀 만큼 오른다. 알코올이면 유리 몫은 2–3%, 수은이면 10–15%.
4. 알코올 온도계의 구는 1 °C만큼의 관 부피의 약 1000배, 수은 온도계는 약 6200배다. 알코올이 더 많이 늘어나니 구가 작아도 된다.
5. 에탄올은 −114 °C에서 얼고 78 °C에서 끓는다. 수은은 −38.8 °C에서 얼고 357 °C에서 끓는다. 그래서 수은 온도계는 −39 °C보다 추운 곳을 잴 수 없다.
6. 알코올 같은 액체는 색이 없어 물감을 탄다. 붉은 액체 온도계에는 알코올 대신 등유나 톨루엔 같은 액체를 넣은 것도 많다(본문: "붉게 물들인 알코올이나 그 비슷한 액체").
7. 팽창 계수는 온도에 따라 조금씩 달라서, 기둥이 온도에 꼭 비례해 움직이지는 않는다. 이 그림은 비례한다고 둔 모형이다.
8. 관 위 끝의 팽창실은 기둥이 오를 때 관 속 압력이 커지지 않게 한다.
9. 최고 최저 온도계(식스 온도계라고도 한다)는 U자 관이다. 왼쪽 관 위 끝의 큰 통에는 알코올이 가득 차 있다(감온 통). 수은은 U자의 굽은 아랫부분에만 있고, 양쪽 관 모두 수은 위는 알코올이다. 오른쪽 관 위 끝의 통에는 알코올이 조금 있고 그 위는 공기(또는 증기)다 — "진공"이라고 쓰지 않는다.
10. 따뜻해지면 감온 통의 알코올이 늘어나 왼쪽 관의 수은을 밀어 내리고, 오른쪽 관의 수은은 그만큼 올라간다. 추워지면 거꾸로 왼쪽 수은이 오르고 오른쪽이 내려간다.
11. 그래서 오른쪽은 최고 온도 눈금, 왼쪽은 최저 온도 눈금이고, 최저 눈금은 거꾸로 되어 있다(위가 낮은 온도).
12. 눈금 표시기는 작은 유리 막대 속에 쇠 철사를 넣은 것이다(길이 약 2 cm). 머리카락처럼 가는 용수철이 관 벽을 눌러, 놓인 자리에 마찰로 머문다. 양 끝은 관보다 가늘어 알코올은 옆으로 흘러 지나가지만, 수은은 표시기를 밀고 올라간다.
13. 수은은 표시기를 밀어 올릴 수만 있고 끌어내리지 못한다. 그래서 표시기는 그 사이 가장 높이 밀려 올라간 자리에 남는다. 읽는 곳은 표시기의 아래 끝(수은에 닿았던 끝)이다.
14. 되돌리기: 쇠로 된 표시기를 바깥에서 자석으로 끌어 수은 면까지 내린다. 단추를 누르는 제품은 관 뒤의 자석 띠가 표시기를 잡고 있다가, 단추를 누르면 자석이 떨어져 표시기가 액체 위로 내려온다.
15. 수은을 쓰지 않는 최고 최저 온도계: 감온 액체로 톨루엔, 수은 자리에 물들인 소금물(브롬화칼슘 용액)을 쓴 제품이 있다. 두 액체는 섞이지 않고 무게가 다르다. 표시기는 자석이 잡는다. — 책의 "액체 합금"은 확인되지 않았다: 본문은 "수은"으로 쓴다.
16. 수은은 독성이 있어, 국제 협약으로 수은 온도계의 제조와 수출입이 줄어들고 있다. 유럽 연합은 일반에 파는 온도계에 수은을 쓰지 못하게 했다(연도는 쓰지 않는다).
17. 제품 예: 눈금 −30…+50 °C, 길이 약 22 cm.
18. 수은 기둥이 흔들려 끊기거나 표시기가 수은에 묻히면 틀린 값을 보인다. 사람이 직접 가서 읽어야 한다. 기상 관측소는 최고 온도계와 최저 온도계를 따로 쓴다(식스 온도계를 기상 관측의 표준 기구라고 쓰지 않는다).
19. 낱말: 구(감온부), 관(모세관), 팽창실, 감온 통, 수은, 알코올, 눈금 표시기, 자석.
20. 이 그림에 대하여 재료: 관은 실제(안지름 0.5 mm)보다 약 5배 굵게 그렸다. 알코올·수은·유리의 팽창 계수를 일정하게 두었다. 오른쪽 통의 알코올 면 변화(0.1 mm 아래)와 수은 자체의 팽창은 그리지 않았다. 온도계가 기온을 따라가는 빠르기는 실제보다 훨씬 빠르게 그렸다.

## 10. 받아들임 기준(1단계)
1. `npm run preview -- thermometer/<VID>`, `CHROME_PATH=$(command -v chromium || command -v google-chrome-stable) npm run check -- thermometer/<VID>` 두 테마 line ends 0, `npm run build`, `npm run check:provenance`
2. 스크린샷을 직접 보고: 두 온도계와 눈금, 최저 눈금이 거꾸로(−30이 위), 표시기 둘이 수은 면과 떨어져 있음(4배 크롭: 최고 표시기 아래 끝이 오른쪽 눈금 24, 최저 표시기 아래 끝이 왼쪽 눈금 6에 맞음), 판독값 "지금 15 °C · 최고 24 · 최저 6"
3. 버튼으로 따뜻하게·차갑게 상태를 즉시 그려(애니메이션 없이) 기둥과 수은이 맞게 움직이는지(30 °C: 빨간 기둥 끝과 오른쪽 수은 면이 30 눈금, 왼쪽 수은 면이 왼쪽 눈금 30)
4. worker_done: 바꾼 파일(git status), 설계서와 다르게 한 점, 설계에 대한 관찰(`out/thermometer/report.md`, `--report-path`)

## 11. 1b 고침 (1단계 검증에서, 조정자) — 두 베리언트 공통
1. **눈금을 관과 같은 깊이에.** 눈금선·숫자·"°C"/"최고"/"최저"는 y = 0(판 앞면)이 아니라 **y = 6(관 중심선과 같은 깊이)**에 투영한다. 판 앞면 y = 0에 두면 elevation 16°의 원근 때문에 같은 z가 화면에서 어긋나, 표시기 아래 끝이 눈금 24가 아니라 22–23에 맞아 보였다(설계자 실수). 판 슬래브는 그대로 y −14…0.
2. **판독값에서 "지금"을 뺀다**: "15 °C · 최고 24 · 최저 6". 음수는 유니코드 빼기 기호 "−"(U+2212). 가장 긴 경우 "−10 °C · 최고 30 · 최저 −10"(22자).
3. **수은은 한눈에 은색 금속으로**: 수은 기둥(r 1.3)은 알코올·빈 유리와 확실히 다른 짙은 금속 회색(예: `color-mix(in srgb, var(--anatomy-mid) 70%, var(--anatomy-shade-0))` 계열)에 관을 따라가는 밝은 줄 하나. 900 px 전체 화면에서 U자 굽은 곳의 수은이 은색 띠로 보여야 한다(4배 크롭 아님).
4. **라벨 지시선은 다른 부품을 가로지르지 않는다.** 특히 "알코올(감온 통)"은 보통 온도계 관을 건너지 않게 판 위쪽 여백(판 윗변보다 위)으로 끌어낸다.
