quad/.claude/session/2026-08-07-03-preref.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

61 lines
4.6 KiB
Markdown

<!-- quad-v2 세션 로그 원문 — CLAUDE.md에서 이전됨(2026-08-11 정리 세션). -->
<!-- 이 파일은 quadnomicon 개발로그 소재용 원자료로, 당시 시행착오(정정 전 서술 포함)를 그대로 보존함. -->
<!-- 현재 유효한 설계는 이 파일이 아니라 base//research//archive/가 최종 소스 — 이 파일 안의 판단이 이후 세션에서 뒤집혔을 수 있음. -->
## 2026-08-07 세 번째 세션 — Ref의 KV 핸들러 처리 vs phase 타이밍, `PreRef` 신설
**출발점**: Ref가 Modifier처럼 밖에서 처리되는 게 아니라 KV 핸들러
(`process(inst,k,v)`)로 처리된다면, "생성 직후"/"자식 마운트 후" 두
콜백 타이밍(특히 self(Instance)를 안 주는 이벤트가 Ref로 self를 얻는
경우)을 단순 for-loop 디스패치만으로 어떻게 표현하는지가 출발 질문 —
길게 이어진 단일 스레드라 아래 요약만 읽으면 됨, 상세 근거는 각 base
문서에 이미 반영됨.
**핵심 결론(전부 `base/bind-system-plan.md`에 반영 완료)**:
- **base 디스패치 드라이버는 props 순회를 "배열 파트(children/Ref) 먼저,
해시 파트(프로퍼티/이벤트) 나중"으로 명시적으로 두 패스 계약화**한다
— Luau 테이블이 실제로 이렇게 순회되는 걸 사용자가 직접 확인했지만,
그 우연한 동작에 기대지 않고 base가 스스로 이 순서를 보장(다른
백엔드가 다른 자료구조를 쓸 수 있어서). M0 스파이크 검증 항목에 추가.
- **`CreatedRef`의 `{phase="created"|"mounted"}` 옵션은 폐기.** 두 패스
계약 덕에 "자식 마운트 전/후"는 그냥 배열 안에서 Ref를 다른 children
보다 앞/뒤에 놓는 것만으로 공짜로 표현됨 — 옵션 문법 자체가 불필요.
- **`PreRef` 신설** — "프로퍼티/이벤트 세팅보다도 먼저"(Roblox의
`ChildAdded`/`DescendantAdded`/`Changed`류가 setup 도중 동기 발화할
수 있어서 self-ref가 이벤트보다 먼저 채워져야 하는 케이스)만 담당하는
별도 nominal 타입. `Ref`를 그대로 재사용(런타임 중복 없음)하되
Modifier 필드 값·Source/Store 값으로는 타입으로 아예 못 들어가게
막고, children 배열 안에서도 위치 무관하게 항상 최우선(호이스팅) —
base 드라이버가 두 패스 루프 앞에 `PreRef`만 골라 fire하는 좁은
pre-pass를 하나 더 둠.
- **일반 `Ref`는 Modifier/Store 어디든 계속 자유롭게 들어감** — Store를
통해 나중에 도착하는 Ref는 그냥 도착한 순간 처리, 별도 phase 개념 불필요.
- **`:Wait()`는 PreRef에도 그대로 유효** — fire 자체는 동기적이지만
호출부가 `task.spawn`이 아니라 순수 `coroutine`일 수 있어 실제
yield-resume이 필요한 경우가 있음. "채워졌는지 먼저 확인, 없으면
`:Wait()`" 방어 관용구를 문서화 대상으로 명시.
- **콜백/대기자 실행 구현 디테일 추가**: 같은 배열 하나를 한 번의
일반화 `for`로 순회하며 `type(v)=="thread"``coroutine.resume`+
슬롯 nil 처리(1회성), 함수면 그냥 호출(유지) — 새 등록은 `table.insert`
로 끝, 성긴 배열이어도 압축 불필요. **[정정, 2026-08-07 열 번째 세션]**
"슬롯 nil 처리"는 틀림 — 사용자가 Luau REPL로 반례 제시, 실제로는
`None`으로 소진해야 함(`#t`/`table.insert` 안전성 문제). `base/
bind-system-plan.md` "왜 `nil`이 아니라 `None`인가" 절이 최신.
- v1의 `OnCreated` 특수 DI 키는 이식 안 함 — `Ref():Callback(fn)`으로
완전 대체.
**역전된 이전 서술은 archive로 이동**: `CreatedRef``phase` 옵션과
"Ref는 특수 처리 없는 평범한 참가자"라는 원래 서술은
`archive/ref-phase-option-reversed.md`로 옮기고 원 위치엔 짧은 포인터만
남김(컨텍스트 비대화 방지 목적, `archive/store-source-proxy-reversed.md`
같은 패턴). `architecture.md` 소스트리 주석/`question.md`(PreRef를
용어 재검토 대상에 추가)/`research/documentation-content-map.md`(stale
`{phase=...}` 예시 갱신)도 같이 동기화함.
**아직 미해결, 다음 세션 주제로 예고됨**: `{ Override = nil, mod }`처럼
인라인 키로 modifier가 주는 값을 명시적으로 "지우고" 싶어도 Lua
테이블 리터럴의 `키 = nil`은 키가 아예 없는 것과 구별이 안 돼서 안
풀리는 문제 — `false`를 이벤트 disconnect 센티널로 쓴 선례처럼 `None`
(가칭) 프리미티브를 도입하는 방향만 `base/modifier-plan.md` "2-1"절에
짧게 메모해두고 상세 설계는 다음 세션으로 미룸.