# [역전됨] `CreatedRef`의 `phase` 옵션 + "Ref는 특수 처리 없는 평범한 참가자" — 위치 기반 순서 + `PreRef` 신설로 대체됨 **역전 일시**: 2026-08-07 (세 번째 세션). **원 확정 일시**: 2026-08-04 (Ref 도입 확정 절)~2026-08-06(Ref 일반화 절)에 걸쳐 누적 확정. **현재 유효한 설계**: `base/bind-system-plan.md`의 "확정된 디스패치 모델" 절 하단(배열/해시 두 패스 계약)과 "`phase` 옵션 폐기 → 위치로 표현, `PreRef` 신설" 절이 최종 소스. 이 파일은 더 이상 능동적으로 참고할 필요 없음(구현에 안 씀) — 왜 "옵션 하나로 phase를 고르는 설계"에서 "위치 기반 순서 + 별도 타입 분리"로 넘어갔는지가 `quadnomicon`(프레임워크 설계자용 심화 콘텐츠) 소재로 가치 있어서 사유·원문을 통째로 보존해둔 것. ## 역전된 사례 — 원래 무엇을 확정했었나 **1. Ref는 dispatch 레지스트리의 "평범한 참가자"였음** (2026-08-04 원문, `ref-plan.md` "Ref — 도입 확정" 절): > **바인드 방법**: 특수 처리 없이, children을 배열 아이템으로 넣듯 > `CreatedRef` 같은 값을 숫자 키 슬롯에 넣는 방식 — child와 동일한 > 층위에서 `process(inst,k,v)` 디스패치를 그대로 타게 함. 즉 Ref도 > pluggable 핸들러 레지스트리의 평범한 참가자. **2. "생성 직후"/"마운트 후" 두 타이밍은 옵션 값으로 골랐음** (같은 절): > **콜백 호출 시점 확정**: 생성 직후(construction 시점)와 트리 마운트 > 후(Parent 세팅 완료) 둘 다 지원 — 옵션으로 선택(`CreatedRef(fn, > {phase="created"|"mounted"})`류, 정확한 API 이름은 구현 단계에서 확정). 당시엔 이 두 문장이 서로 모순되지 않는다고 봤음 — "평범한 참가자"이면서 동시에 "옵션으로 두 시점 중 골라 fire"하는 게 가능하다고 전제했던 것. ## 역전된 이유 실제 사용 시나리오를 짚다가 드러남: quad-roblox 이벤트는 `self(Instance)`를 안 주기로 이미 확정돼 있어서(`base/bind-system-plan.md` "이벤트 핸들러는 self를 받지 않는다"), 이벤트 안에서 인스턴스 자신을 참조하려면 Ref로 받아둔 값을 읽는 수밖에 없음. 그런데 Roblox 이벤트 중 일부(`ChildAdded`/ `DescendantAdded`/`Changed`류)는 유저 인터랙션을 기다리지 않고 **setup 도중 프로퍼티 대입/Parent 세팅 자체의 부작용으로 동기적으로 발화**할 수 있음 — 이 시점에 self-ref가 아직 안 채워져 있으면 그대로 터짐. 이 문제를 실제로 풀려고 보니 "phase 옵션 하나로 고르는 평범한 참가자" 모델이 두 가지를 보장하지 못한다는 게 드러남: 1. **"평범한 참가자"라는 전제 자체가 Modifier/Store를 거치면 깨짐.** Ref가 Modifier 필드로 flatten되거나 Source/Store 값으로 나중에 도착하면, "이 인스턴스에 다른 무엇보다 먼저"라는 순서 보장을 구조적으로 줄 방법이 없음(Modifier flatten은 해시 파트로 합쳐지고, Store 값은 process/retract 재귀 경로로 원래 스캔보다 나중에 도착하므로). 2. **"created" phase가 실제로 뭘 보장하는지가 원래 정의돼 있지 않았음.** "생성 직후"가 "다른 모든 프로퍼티/이벤트보다 먼저"까지 보장하는 건지, 아니면 "그냥 루프 어딘가에서, construction 이후"면 충분한 건지가 불명확했음 — 후자로 해석하면 옵션이 무의미해지고, 전자로 해석하면 `process(inst,k,v)` 우선순위 스캔만으로는 줄 수 없는 순서 보장이라 드라이버 레벨 개입이 필요해짐. ## 이전 것과 지금 것의 차이 | | phase 옵션(역전됨) | 위치 기반 + `PreRef`(현재) | |---|---|---| | "자식 마운트 전/후" 표현 | `{phase="created"\|"mounted"}` 옵션 값 | children 배열에서 다른 형제보다 앞/뒤에 놓는 것만으로 결정(두 패스 계약 위에서 공짜로 나옴) | | "프로퍼티/이벤트보다 먼저" 표현 | 같은 옵션의 `"created"` 값 — 실제로 이 보장을 줄 메커니즘은 없었음 | 별도 nominal 타입 `PreRef` — Modifier/Store엔 타입으로 아예 못 들어가고, 배열 파트 스캔 전에 driver가 따로 pre-pass로 fire, 위치와도 무관하게 항상 최우선(호이스팅) | | Ref/CreatedRef가 참가자로서 특수한지 | "특수 처리 없이, 평범한 참가자"라고 명시 | 일반 Ref/CreatedRef는 여전히 평범한 `(v=Ref)` 핸들러 매치 — 다만 그 매치가 성립하려면 base가 배열 파트/해시 파트 순서를 **명시적으로 계약화**해야 했음(우연한 Luau 테이블 동작에 기대지 않음), `PreRef`는 아예 별도 pre-pass 대상이라 진짜 특수 취급 | | Store/Modifier 조합 가능 범위 | 논의 안 됨(암묵적으로 전부 가능하다고 전제) | 일반 Ref는 자유, `PreRef`는 타입으로 원천 차단 | ## 왜 완전히 헛수고는 아니었나 "children 배열 슬롯에 넣으면 dispatch가 채워준다"는 `CreatedRef`의 핵심 아이디어 자체는 그대로 살아남음 — 바뀐 건 "그 안에서 두 타이밍을 옵션 하나로 고르게 하자"는 세부 설계뿐. 오히려 이 반전 덕분에 "왜 굳이 `PreRef`라는 별도 타입이 필요한가"(=순서 보장이 안 되는 경로가 실제로 있다는 것)와 "base 드라이버가 왜 배열/해시 순서를 명시적으로 계약화해야 하는가"(=Lua 테이블의 우연한 동작에 기대면 다른 백엔드에서 깨질 수 있다는 것) 두 가지가 훨씬 선명해짐 — `quadnomicon`에서 "옵션 하나로 퉁치려던 설계가 실제 시나리오(Roblox 이벤트의 동기 발화)를 만나 타입 분리로 귀결된 사례"로 쓰기 좋음.