research: Context 난이도 판정 완료 — 기각 권고, 레이어드 Store 대안 확정

서브에이전트 조사: 동기 콜스택 한정 버전은 구현 난이도 낮지만(Fusion
Contextual 이식 가능), quad가 정상 패턴으로 확정한 Slot 비동기 추가에서
조용히 defaultValue로 폴백하는 함정 있음 + quad-debug의 "모든 연결은
선언된 그래프" 철학과 충돌. Roblox Luau는 thread-local이 없어 완전 자동
버전은 플랫폼 한계로 사실상 불가(Node AsyncLocalStorage/Python contextvars와
동일 문제). 대안 비교 결과 레이어드 Store(__index 델리게이션, Modifier/
Source가 이미 쓰는 패턴과 동일 계열)가 서브트리 오버라이드 가치를 명시적
전달 철학·비동기 안전성 유지하며 대부분 재현 — 최종 권고안으로 채택.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
qwreey 2026-08-06 22:04:54 +09:00
parent ec5fb8184b
commit 67438a172e
Signed by: qwreey
GPG key ID: D28DB79297A214BD
2 changed files with 86 additions and 38 deletions

View file

@ -17,15 +17,22 @@
Fusion/Vide/quad v1 소스를 서브에이전트 2개로 병렬 조사 완료, 상세는
`research/additional-primitives-plan.md`. 요지:
- **키 기반 동적 컬렉션 재조정(가장 시급)**: Fusion `ForPairs`/`ForKeys`/
`ForValues`, Vide `indexes()`/`values()`, React `key` prop에 대응하는
프리미티브가 quad엔 전혀 없음 확인 — `Slot`은 CRUD 껍데기일 뿐 diff
엔진이 아님. 인벤토리/리더보드/채팅로그 같은 실전 리스트 UI에 직결.
Slot 확장으로 갈지 별도 프리미티브(가칭 `Keyed`/`ForEach`)로 갈지부터
전혀 정해진 게 없음 — 사용자 판단 필요.
- **키 기반 동적 컬렉션 재조정(가장 시급, 설계 스케치 진행 중)**: Fusion
`ForPairs`/`ForKeys`/`ForValues`, Vide `indexes()`/`values()`, React
`key` prop에 대응하는 프리미티브가 quad엔 전혀 없음 확인 — `Slot`
CRUD 껍데기일 뿐 diff 엔진이 아님(`pre-implementation-audit.md` 1-7번과
같은 지점). 가칭 `state:Keyed(keyFn, renderFn) -> Slot`(State의 파생
데이터 메소드로 프레이밍, `state:Observer(fn)`와 같은 논리) 스케치
진행 중 — 사용자 피드백 반영 중, 상세는 `research/additional-primitives-plan.md`
"진행 중 논의" 절.
- Effect/Watch(자동 cleanup 공개 API), Batch/Transaction(이벤트 store-bind
churn 문제 직결), Context(트리 전파, 단 `purity-and-effects-plan.md`
이식성 원칙과 상충)는 부차적 후보로 확인, 착수 여부 미정.
churn 문제 직결)은 부차적 후보로 확인, 착수 여부 미정.
- **Context — 기각 권고로 결론(2026-08-06, 서브에이전트 난이도 판정
완료)**: 완전 자동 버전은 Roblox Luau 플랫폼 한계로 사실상 불가, 얕은
버전(동기 콜스택 한정)도 quad가 정상 패턴으로 확정한 Slot 비동기 추가에서
조용히 깨짐 + quad-debug의 "모든 연결은 선언된 그래프" 철학과 충돌 —
대신 **레이어드 Store**(`__index` 델리게이션 기반 서브트리 오버라이드)를
실제 필요 시점에 검토할 것으로 대체 결정.
- Untrack/Suspense/Error Boundary/Readonly는 조사 결과 새 프리미티브 없이
기존 설계·Lua 자체 기능으로 이미 충분한 것으로 판단.

View file

@ -33,7 +33,7 @@ Fusion/Vide/react-lua 강점/약점 비교)와는 다른 질문 — 그 문서
| 키 기반 동적 컬렉션 재조정 | **진짜 빈 자리** | 높음 — 가장 시급 |
| Effect/Watch(자동 cleanup 포함 사이드이펙트) | 진짜 빈 자리 | 중간 |
| Batch/Transaction | 부분적 빈 자리(이미 문서화된 churn 문제와 직결) | 중~낮 |
| Context(트리 하위 암묵 전파) | 부분적 빈 자리, 철학과 상충 | 낮음 |
| Context(트리 하위 암묵 전파) | **기각 권고**(난이도 판정 완료, 2026-08-06) — 레이어드 Store로 대체 | - |
| Untrack/Peek | 빈 자리 아님 | - |
| Suspense/비동기 경계 | 빈 자리 아님(문서화 문제로 재분류) | - |
| Error Boundary | 빈 자리 아님 | - |
@ -122,25 +122,31 @@ State에 이벤트를 직접 물리면... churn 비용"). 즉 여러 Source가 `
Vide `batch.luau:4-21`가 정확히 이 역할(여러 `source:set()`을 하나의
flush로 묶음).
## 4. Context(트리 하위 암묵 전파) — 부분적 빈 자리, 철학과 상충
## 4. Context(트리 하위 암묵 전파) — 난이도 판정 완료, 기각 권고 (2026-08-06)
Fusion `Utility/Contextual.luau:28-88`(코루틴 스택 기반 스코프 값), Vide
`context.luau:14-72`(scope 그래프 조회)가 대응 개념. quad는 컴포넌트
경계를 named parameter로만 넘기기로 이미 확정했고(`component-composition-plan.md`),
Context는 본질적으로 "명시적 전달을 건너뛰는" 도구라 quad의 명시성 철학과
다소 충돌함.
Fusion `Utility/Contextual.luau:28-88`(코루틴 키 weak table push-pop), Vide
`context.luau:14-72`(scope 그래프 조회)가 대응 개념. 서브에이전트에게 구현
난이도 평가를 위임한 결과(상세 근거는 아래 "진행 중 논의" 절의 Context
서브섹션 참고), **동기적 저작 트리 안에서만 작동하는 얕은 버전은 구현
난이도가 낮지만(Fusion 코드를 사실상 그대로 이식 가능), quad가 이미
정상 패턴으로 확정한 "Slot에 이벤트 핸들러/코루틴에서 비동기로 자식이
추가되는 경우"엔 조용히 기본값으로 폴백하는 함정이 생김**을 확인 —
Roblox Luau에 thread-local/async-context-propagation 훅이 없어 완전
자동화는 사실상 불가능(플랫폼 한계, 구현 노력의 문제가 아님). Node
`AsyncLocalStorage`/Python `contextvars`가 같은 문제를 "자동"이 아니라
"async 경계마다 명시적 캡처+재진입"으로 푸는 것과 동일한 결론.
Roblox ModuleScript의 `require()` 캐싱이 사실상 싱글톤 전역 접근점 역할을
자연스럽게 하므로(`local Theme = Store({...})`를 모듈로 export해서 아무
컴포넌트에서나 require해 직접 읽으면 됨), **단일 게임 내부 UI 시나리오에선
이게 사실상 Context 대체재로 충분**함 — quad가 이미 허용하는 "Store는
부작용 허용" 철학과도 맞음. 다만 (a) 서브트리별 스코프 분리가 안 되고,
(b) `purity-and-effects-plan.md`의 이식성 원칙과 정면 충돌함(여러 게임에
배포할 재사용 가능한 컴포넌트 라이브러리를 만들려는 순간, 테마 하나
넘기려고 모든 중간 레이어에 `props.Theme`를 수동으로 계속 꿰어야 하는
전형적 prop-drilling이 그대로 남음). "라이브러리로서의 지속 가능성"이라는
프로젝트 목표와는 긴장 관계 — artworks에서 이걸 뒷받침할 "수동 전역
context 테이블 전달" 패턴은 확인 못 함(급하지 않다는 방증).
**기각 권고 근거**: 얕은 버전조차 (a) quad가 스스로 정상 패턴으로 확정한
Slot 비동기 추가에서 가장 먼저 깨지고, (b) `research/debug-tooling-plan.md`
"모든 연결은 선언된 그래프여야 한다"는 quad-debug 철학과 충돌하는 안 보이는
채널을 만듦. 대신 **레이어드 Store**(자식 Source 모음이 자기한테 없는 키
모로 `__index` 폴백 — Modifier/Source가 이미 쓰는 델리게이션 패턴과 동일
계열)가 Context의 핵심 가치(서브트리별 오버라이드)를 명시적 전달 철학과
비동기 안전성을 유지하면서 대부분 재현함 — 오버라이드 Store 참조는 여전히
prop으로 명시 전달해야 하지만, 이건 `component-composition-plan.md`가 이미
확정한 비용이라 새로 감수하는 게 아님. Roblox `require()` 캐싱 싱글톤(단일
전역, 서브트리 오버라이드 불가)은 오버라이드가 필요 없는 단순 케이스에는
여전히 충분.
## 빈 자리 아닌 것으로 확인된 것들
@ -175,9 +181,8 @@ context 테이블 전달" 패턴은 확인 못 함(급하지 않다는 방증).
2. **Effect 공개 API**`process`/`retract`가 이미 증명한 패턴을 얇게
노출하는 정도라 구현 비용 낮음.
3. **Batch** — 이미 문서화된 churn 문제의 직접 해법, opt-in 유틸 수준.
4. **Context** — 급하지 않음(Roblox `require` 캐싱이 단일 게임 시나리오는
충분히 대체), 다만 "재사용 가능 컴포넌트 라이브러리"를 장기 목표로
본다면 재검토 가치 — `purity-and-effects-plan.md`와 같이 봐야 함.
4. **Context** — 기각 권고(2026-08-06 난이도 판정 완료). 서브트리 오버라이드가
실제로 필요해지면 Context가 아니라 **레이어드 Store**를 검토할 것.
## 진행 중 논의 (2026-08-06 후속 세션)
@ -227,18 +232,54 @@ quad는 `renderFn(key, itemState)`가 항상 key+itemState를 다 주고 안 쓰
**다음 단계**: 사용자 피드백 반영해 스케치 다듬고, 이견 없으면 M6(Slot)
착수 시점에 `slot-plan.md`/`bind-system-plan.md`에 정식 반영.
### Context — 구현 난이도 판정 진행 중
### Context — 구현 난이도 판정 완료 (2026-08-06)
사용자 확인: Context는 React `useContext`(Provider가 위에서 값을 심고,
하위 어디서든 prop 없이 읽는 패턴)와 같은 개념 맞음. 구현 난이도가
채택 여부를 사실상 결정한다는 사용자 판단에 따라 서브에이전트에게
난이도 평가 위임(진행 중) — 특히 "컴포넌트 호출 자체는 동기적 콜스택으로
Context 전파가 가능해 보이지만, Slot에 나중에(이벤트 핸들러/코루틴에서)
비동기로 추가되는 자식까지 자동으로 따라오게 하는 건 훨씬 어려워 보인다"는
사용자 직관을 검증 대상으로 명시. 결과는 후속 라운드에 반영.
하위 어디서든 prop 없이 읽는 패턴)와 같은 개념 맞음. 구현 난이도가 채택
여부를 사실상 결정한다는 사용자 판단에 따라 서브에이전트에게 난이도 평가를
위임했고, 결과 수렴 완료 — **위 "4. Context" 절이 최종 결론**. 요지만
재정리:
사용자가 제시한 대안(Store 병합, 타입 안전 서브셋 전달, 레이어 계층을 가진
Store)도 같은 서브에이전트에게 난이도/효용 비교를 같이 시킴.
- **난이도 등급**: 동기적 저작 트리에 한정한 얕은 버전 = **낮음**(Fusion
`Contextual`의 "코루틴을 키로 하는 weak table push-pop"을 그대로 이식
가능, quad-base 단독으로 완결, quad-roblox 분리조차 불필요). 완전
자동(비동기 Slot 추가까지 자동 전파) = **매우 높음, 사실상 불가**
Roblox Luau에 thread-local/async-context-propagation 훅이 없어서 quad가
소유하지 않는 임의의 콜백 경계(`Signal:Connect`, `task.spawn`, Promise)를
가로챌 방법이 없음. 이건 quad 설계의 문제가 아니라 플랫폼 자체의 한계.
- **사용자의 직관이 정확히 맞았음**: 동기 콜스택 부분은 싸지만, Slot에
나중에(이벤트 핸들러/코루틴에서) 비동기로 자식이 추가되는 케이스에서
Fusion 방식은 **에러 없이 조용히 `defaultValue`로 폴백**함 — "테마가
가끔 기본값으로 보인다"는 형태의 원인 추적 어려운 버그를 만듦(Vide
방식은 폴백 대신 에러를 던지지만, 애초에 이 시나리오를 지원 대상으로
설계한 적이 없어 참고할 답이 없기는 마찬가지). Node
`AsyncLocalStorage`/Python `contextvars`도 "자동"이 아니라 "async 경계마다
명시적으로 값을 캡처해서 재진입"하는 방식으로 같은 문제를 풀고 있어,
이식해도 결국 "prop drilling 비용"이 "매 async 경계 캡처+재진입 보일러
플레이트 비용"으로 자리만 옮김.
- **기각 이유(효용 대비 비용)**: 얕은 버전조차 채택할 가치가 낮음 — (1)
`base/slot-plan.md`가 정상 패턴으로 명시한 Slot 비동기 추가에서 가장
먼저, 가장 조용히 깨짐, (2) `research/debug-tooling-plan.md`의 "모든
연결은 선언된 그래프여야 한다"는 quad-debug 철학과 충돌하는 안 보이는
채널을 만듦.
- **대안 비교 결과**: Store 병합(난이도 매우 낮음, 그러나 drilling 자체는
안 없앰) < 타입 안전 서브셋 전달(난이도 낮음, 하지만 트리 전파 문제와는
별개 축이라 해결책이 아님) < **레이어드 Store**(난이도 낮음~중간, Context의
핵심 가치인 "서브트리별 오버라이드"를 대부분 재현하면서 명시적 전달
철학·비동기 안전성 둘 다 유지 — Modifier의 제네릭 `__index` 트릭,
Source가 State를 만족시키는 `__index` 델리게이션과 같은 이미 검증된
패턴 계열이라 구현 리스크도 낮음) — **레이어드 Store가 최종 권고안.**
사용자가 스스로 "어려울 것 같다"고 짐작했던 것과 달리, quad2-try에서
금지한 `Class:Extend()`류 행위(메소드) 상속과는 다른 층위(순수 데이터
값 조회의 `__index` 폴백일 뿐)라 실제로는 낮은 난이도로 판정됨 — 다만
Luau 타입 레벨에서 "자식 키 부모의 나머지 키"를 구조적으로 표현하는
부분은 M0가 이미 검증 대상으로 잡은 `Source<T> satisfies State<T>`
솔버 위험과 비슷한 급이라, 도입한다면 같은 스파이크에 끼워 검증하는 게
합리적.
- **결론**: Context라는 이름의 범용 프리미티브는 채택하지 않음. 테마/로케일
서브트리 오버라이드가 실제로 필요해지는 시점에 레이어드 Store를 별도
후보로 검토(지금 착수 우선순위 낮음, 키 기반 컬렉션 재조정이 여전히
더 시급).
## 참고: 조사에 사용한 소스 근거