quad/.claude/session/2026-08-10-03-tween-redesign.md
qwreey 1f56c75978
chore(docs): split CLAUDE.md session log into .claude/session/, keep 2-4 line summaries
CLAUDE.md had grown to 3196 lines of accumulated session logs, causing
context bloat. Full session narratives (including trial-and-error and
later-corrected reasoning — quadnomicon devlog raw material) now live
as 39 individual files under .claude/session/. CLAUDE.md keeps only a
short "지금 할 일" (re-synced against question.md/pre-implementation-audit.md,
stale detail dropped) and a compact per-session summary+link table.
No design decisions changed; base/research/question.md were already
in sync with every session (verified against README.md/question.md
before archiving), so no unreflected content needed migrating first.
2026-08-11 14:40:43 +09:00

118 lines
9 KiB
Markdown

<!-- quad-v2 세션 로그 원문 — CLAUDE.md에서 이전됨(2026-08-11 정리 세션). -->
<!-- 이 파일은 quadnomicon 개발로그 소재용 원자료로, 당시 시행착오(정정 전 서술 포함)를 그대로 보존함. -->
<!-- 현재 유효한 설계는 이 파일이 아니라 base//research//archive/가 최종 소스 — 이 파일 안의 판단이 이후 세션에서 뒤집혔을 수 있음. -->
## 2026-08-10 두 번째 세션 — Tween 구조 전면 재설계: 독립 Dispatch 핸들러 →
값-레벨 `Tween<T>` 래퍼, `pre-implementation-audit.md` 1-1 완전 해소
사용자가 "트윈도 타입 문제가 있다 — 키 타입을 어떻게 하냐, Property
setter가 더 분발해서 `V``isTween`이면 트윈 넣는 게 낫지 않냐"고
제기하며 시작된 긴 단일 스레드. 기존 확정 모델(`[Tween(key,
tweenData...)] = storeValue`, `v`가 Store인 아무 `k`나 잡는 우선순위
최상위 Dispatch 핸들러, 2026-08-04부터 확정)이 실은
`research/pre-implementation-audit.md` 우선순위1-1이 이미 지적해뒀던
구조적 모호함("애니메이션 없는 일반 반응형 프로퍼티 바인딩도 결국
이름이 Tween인 파일을 거쳐가는가")을 안고 있었다는 걸 사용자 제안이
정확히 겨냥한 것으로 드러나, 세션 내내 살을 붙여 완전히 재설계까지
감. 구 모델은 `archive/tween-special-bind-key-reversed.md`로 이전,
`research/tween-plan.md`는 전면 재작성됨 — 상세 근거는 그 두 문서가
최종 소스, 여기는 결정 흐름만 요약.
**핵심 재설계**: State/Source 언랩(범용 `Dispatch/StoreBind.luau`, `k`/`v`
타입 무관)과 "이 값이 트윈 대상인가" 판단을 완전히 분리 — 후자는 별도
Dispatch 핸들러/우선순위 경쟁이 아니라, **PropertyHandler가 `realv`
다 풀어낸 뒤 직접 하는 값-레벨 분기**(`isTween(realv)`)로 옮김. `Tween(opts:
{Value: T, ease...}) -> Tween<T>``Store({...})`와 같은 `Type(args)`
테이블 팩토리. 이 전환 하나로 우선순위1-1이 구조적으로 성립 불가능해짐
— 범용 반응형 바인딩과 Tween이 애초에 같은 핸들러를 놓고 경쟁할 지점
자체가 없어짐.
**세션 중 순서대로 다듬어진 세부 결정들**(전부 최종적으로 `research/
tween-plan.md`에 반영):
1. **`Tween.Value`는 plain `T`만, 자체 반응 경로 없음** — 처음엔 `Value`
`T|State<T>`를 받아 내부에 별도 Observer를 걸어야 하나 검토했으나,
바깥 `:Compute`가 소스 변경마다 `Tween{Value=v,...}`를 통째로 재생성해
StoreBind 재귀를 타므로 불필요함을 확인 — "같은 일 하는 두 번째 경로를
안 만든다" 원칙 재적용, `Tween<T> = {Value: T, ease...}`로 확정.
2. **3-상태 릴레이션 슬롯으로 `hasBeenSet`과 활성 엔진 트윈 저장을 통합**
`relate:GetStrong(inst,k)``RobloxTween | true | nil` 중 하나:
`nil`=이 키 첫 세팅(애니메이션 없이 즉시 스냅, 기본값→목표값으로
날아오는 진입 애니메이션 버그 방지), `true`=세팅된 적 있음/활성 트윈
없음(정상 애니메이션 시작 가능), 엔진 객체=활성 트윈 있음(override
정책대로 정리 먼저). 사용자가 직접 "hasBeenSet은 어차피 트윈에만
쓰이니 트윈 저장 슬롯 하나로 합치자"고 제안해 확정.
3. **활성 트윈이 있는데 plain 값이 들어오는 경우의 순서 규칙 신설**
먼저 override 정책대로 이전 트윈을 정리(멈추거나 끝냄)하고, **그
정리가 끝난 뒤에만** 새 값을 세팅. 순서가 뒤바뀌면 이전 트윈의 다음
인터폴레이션 프레임이 방금 세팅한 값을 덮어쓸 위험이 있어서 — 사용자가
직접 짚은 시퀀싱 버그.
4. **타입 대수: `T' = T | Tween<T>` 치환만으로 해결, 새 타입 기계 불필요**
지금 프로퍼티류 필드가 전부 `T | State<T>` 모양으로 통일돼 있는데,
여기서 "이 필드의 `T`" 자체를 `T' = T | Tween<T>`로 치환하면 자동으로
`T | Tween<T> | State<T | Tween<T>>`가 나옴 — Modifier/State/Source/
StoreBind 코드엔 `Tween` 인지 로직이 전혀 안 들어감(StoreBind는 원래도
페이로드 타입에 무관하게 `isState`만 봄), 타입 생성 스크립트가 필드
타입 문자열만 바꾸면 끝. 사용자가 직접 대수적으로 도출.
5. **`useTween` 우회 — 새 옵션 필드 없이 해소.** 이전엔
`Tween{useTween=state<boolean>}`처럼 별도 필드가 필요하다고 열어뒀으나,
2026-08-07 일곱 번째 세션에 확정된 `state:Apply(factory)` sugar 위에
`someState:Apply(Animate(reduceMotion, opts))`처럼 조건부로 `Tween{...}`
씌우거나 안 씌우는 `:Compute` 팩토리 하나로 공짜로 풀림 — 새 base
메커니즘 불필요.
6. **`Animate` 콤비네이터는 quad-roblox 유틸, base 프리미티브 아님** —
`Tween<T>` 값 타입/`isTween`만 base(`quad-base/Tween.luau`)에 있고,
`Animate`는 이미 있는 `:Apply`/`:Compute`/`Tween{...}`를 조합한 편의
함수라 나중에 이름/모양을 자유롭게 바꿔도 base 계약에 영향 없음 —
사용자 표현으로 "저비용 고효율 엔지니어링".
7. **패키지 경계는 Tag가 이미 밟은 분리를 그대로 재사용** — quad-base:
`Tween.luau`(값 타입만). quad-roblox: `Handlers/Property.luau`(isTween
분기+3-상태 저장+override 정책 흡수, 기존 독립 `Handlers/Tween.luau`
폐기) + `Animate.luau`(신규).
8. **부수 발견 — `retract`가 Tween 경로에서 사실상 필요 없어짐.** 기존
모델에서 "Tween↔프로퍼티 핸들러 타입 교체"가 `retract`가 실제로
의미를 갖는 유일한 대표 예시였는데, 새 모델에선 매치되는 Dispatch
핸들러가 항상 PropertyHandler 하나뿐이라 이 케이스 자체가 사라짐 —
트윈 취소/전환은 PropertyHandler 내부의 3-상태 슬롯 로직으로 대체(Tag가
이미 하는 "diff는 process 자신이 담당" 패턴과 같은 모양). `retract`
필드 자체는 "생략 불가" 일반 규칙이라 여전히 정의는 해두되, 실제
호출은 거의 없어짐. Tag(핸들러 타입이 실제로 바뀌게 재설계되어
`retract`가 필요해진 사례)와 Tween(핸들러 타입이 안 바뀌게 재설계되어
`retract` 필요성이 사라진 사례)을 서로 반대 방향 사례로 archive 문서에
대비해둠 — quadnomicon 소재.
9. **`Tween<T>`의 핸들러 계층 분류 정정** — `base/modifier-plan.md`
원래 Tween을 Slot/Tag/Attribute와 같은 "dispatch 참가자"(State/Source에
담겨도 재귀 재-dispatch가 그대로 처리해주는 부류)로 묶어뒀는데, 이제
`Tween<T>``process`/`retract`가 없는 순수 raw 데이터 값이라 `None`
같은 분류로 정정 — Modifier 필드/`State<Modifier>`가 막는 "핸들러
계층 값 → error" 규칙에 안 걸린다는 결론은 안 바뀜(그냥 raw 값이라서로
근거가 바뀜).
10. **`initValue`(진입 애니메이션)와 hasBeenSet의 긴장 관계를 기록만
해둠** — hasBeenSet이 "첫 세팅은 무조건 스냅"을 기본 동작으로
확정했으므로, 나중에 `initValue`(다이얼로그 슬라이드-인 등)가 실제로
필요해지면 이 억제 동작을 명시적으로 우회하는 방법까지 같이 설계해야
함 — 새 결정 없이 상충 관계만 `research/tween-plan.md`에 남김.
**여전히 열려있는 것**(M11 착수 시 확정): override 정책 4가지 중 기본값
Cancel 외 세 옵션의 정확한 키 이름/시그니처, Tween→plain 전환에 5번째
옵션이 필요한지, 트윈 옵션 값 모양(`TweenInfo` 그대로 vs 편의 필드 — 소견은
후자), `Animate`의 정확한 시그니처(조건/옵션 분리 vs 통합).
**반영된 파일**: `research/tween-plan.md`(전면 재작성, 최종 소스),
`archive/tween-special-bind-key-reversed.md`(신규, 구 모델 원문+역전
사유), `base/bind-system-plan.md`(9곳 — "확정된 디스패치 모델"의 대표
예시를 Tween에서 StoreBind로, `retract` 필요 패턴 예시를 Tag로 교체,
"Dispatch는 프리미티브가 아니다"/"Dispatch 체인" 절의 핸들러 목록에서
Tween 제거, `None` 센티널 절 예시 갱신, Ref/Brand 절 문구 정정),
`base/architecture.md`(소스트리 — `quad-base/Tween.luau` 신설,
`quad-roblox/Handlers/Tween.luau` 삭제하고 `Handlers/Property.luau`
설명에 흡수, `Animate.luau` 신설), `base/modifier-plan.md`(핸들러 계층
분류에서 Tween 제외 + 신규 "10. `Tween<T>`와의 타입 합성" 절),
`research/pre-implementation-audit.md`(우선순위1-1 해소 표시),
`ROADMAP.md`(M11 전면 재작성, M2/M7 체크박스 갱신), `.claude/question.md`/
`.claude/README.md`(참조 동기화).
**다음 세션이 할 일**: 안 바뀜(`ROADMAP.md` M0부터, luau-test 결과 확인
우선) — 이번 세션도 순수 설계 확정/문서 정리라 M0 착수 우선순위 자체는
그대로. M11 착수 시점이 오면 위 "여전히 열려있는 것" 목록부터 확인.