qa: Claim 갈래 8건 확정 — research/existing-mount-plan → base/claim-plan.md 승격, 루트 .Parent= 밖에서 허용 복원

- 루트 키 센티널(맨 테이블+Claim<<T>> 기각), type <Class>Param 필드 파트 공유, Claim 타입 인자 없음
- 디스크립터 순서 정본 / claim된 부모 안 New 자식 허용(위치는 프로바이더 몫) / 이름 Claim·D.Mapper
- §5-7: Claim은 1회·전체 소유, H-146 루트 .Parent= 예외를 좁혀 복원(H-148 폐기 → 같은 날 복원)
- debug 검사 범위는 research/debug-tooling-plan.md로 이관, nativeFindChild 주입 op 등록
- 감사 8라운드 수렴(2→3→1→3→2→0→4→0) + /code-review high 10건 반영 — 새 메커니즘 넷은
  base/claim-plan.md §10 + question.md "M5 착수 전" 절로(gcconn/gchold 셋업 자리 /
  이중 claim 레지스트리 / PlayerGui own-all vs ResetOnSpawn / <Class>Param 배열 파트)

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
This commit is contained in:
qwreey 2026-08-28 17:23:30 +09:00
parent f020f3f36f
commit 253d141096
Signed by: qwreey
GPG key ID: D28DB79297A214BD
23 changed files with 621 additions and 223 deletions

File diff suppressed because one or more lines are too long

View file

@ -3,7 +3,7 @@
> **⛔ [2026-08-14 세션, 사용자 확정 — 기각]** `research/`에서
> `archive/`로 이전. **더 이상 "열린 가능성"이 아니라 미지원으로 확정.**
>
> **⭐ [2026-08-28] 좁은 형태로 부활 — `research/existing-mount-plan.md`.** 여기서
> **⭐ [2026-08-28] 좁은 형태로 부활 — `base/claim-plan.md`.** 여기서
> 기각된 것은 *"이미 있는 Instance에 나중에 새 props를 다시 바인드"*이고 그
> 사유(바깥이 자식 구성을 밀고 당기면 부기가 깨진다)는 그대로 유효하다. 부활한
> 것은 그 반대 방향 — **한 번 `Claim`하면 quad가 소유하고 직계 자식은 사용자가

View file

@ -1292,3 +1292,16 @@ L2 디스패치 Handler · Dispatch 코어 · chains · None · ┐
히스토리 문서라 소급 수정하지 않았다.** 2026-08-24 이전에 쓰인 그 문서들의
`M2`/`M3`는 **옛 의미**(M2=디스패치, M3=반응형)로 읽을 것. 이 경고는
`ROADMAP.md`의 M2 배너에도 있다.
## [해소됨, 2026-08-28] `Claim` 갈래 — `research/existing-mount-plan` §5 여덟 항목
**2026-08-28 10라운드 `H-148`에서 신설된 research 문서의 §5 갈래 여덟 개가 같은 날
사용자와 대화형으로 전량 확정돼 `base/claim-plan.md`로 승격됐다** — 결정과 사용자
원문은 그 문서 §7, 대화 원문은 `session/2026-08-28-02-claim-promotion.md`. 요지:
루트 키는 센티널(맨 테이블 + `Claim<<T>>`는 타입 자동완성 손실로 기각) / 디스크립터
순서 정본 / claim된 부모 안 `New` 자식 허용(위치는 프로바이더 몫) / debug 검사 범위는
`research/debug-tooling-plan.md`로 이동 / 이름 `Claim` + `D.Mapper` / M5 /
**§5-7(여러 스크립트가 한 `PlayerGui`)은 `Claim` 1회·전체 소유 유지 + 루트의
`.Parent =`는 밖에서 허용으로 복원**(사용자: *"정확히는 두번 Claim 불가하다는 의미.
필요하다면 Slot 을 안에 만들고 리턴하는 중간 모듈을 만들어야함. 밖에서 .Parent
설정하는건 괜찮아"*) / 매핑된 정적 자식은 같은 `InstanceChildHandler`.

View file

@ -297,7 +297,7 @@ quad/
├── pesde.toml # quad-base가 아니라 quad-types에만 workspace 의존
└── src/
├── RobloxFactory.luau # BaseModule 뮤테이션, 재호출 가드(같은 팩토리=무시/다른=에러) — 주입 대상 목록은 아래 EngineOps.luau 줄이 소스 — 여기서 다시 나열하지 않는다(**[2026-08-22]** 예전엔 addTag/removeTag/setAttribute까지만 적혀 있어 native*/setTimeout이 빠져 있었음). bindLifetime/canBound/canExecute도 같은 경로로 주입됨
├── EngineOps.luau # 주입되는 엔진 op 구현: addTag(inst,{string})/removeTag(inst,{string})=CollectionService, setAttribute(inst,name,v)=inst:SetAttribute(v==nil이면 삭제), nativeDispose(inst)=inst:Destroy()(`dispose(value)`가 `isSlot`이 아닐 때 위임, `base/slot-plan.md`), **[2026-08-21 5라운드 신설, 이름 확정] `native*` 물리 트리 조작 계층** — nativeInsert/nativeExtract/nativeRemove/nativeMove/nativeSwap(0-based 절대 offset + 대상 요소 배열을 받음; Roblox는 offset을 무시하고 배열을 쓰고 DOM은 둘 다 씀). 미주입이면 에러가 아니라 **조합 폴백**. **[2026-08-22 추가] 시간 op 둘** — setTimeout(func, delay) -> Timeout / clearTimeout(t), Roblox는 task.delay/task.cancel로 배선(**인자 순서가 반대라 주의**); `Debounce`/`Throttle`이 얹힐 때 필요하고 그 전엔 미주입이어도 무방(`base/debounce-throttle-plan.md`). **이 줄이 주입 op 전체 목록의 단일 소스다** — 다른 문서는 개수를 세지 말고 여기를 가리킬 것 (`base/dispatch-core-plan.md` "base가 소유하는 핸들러와 주입되는 엔진 op" 절). **[2026-08-24 6라운드 신설] `isInst(value): boolean`**(`H-40` — 요소 타입 검증을 화이트리스트로 뒤집으면서 생긴 판정 술어, quad-roblox는 `typeof(value) == "Instance"`)**와 `onDestroying(inst, fn): Connection`**(`H-11` — `Effect`의 leaf 사망 cleanup을 발화시키는 훅, `bindLifetime``isEffect`일 때 부른다, quad-roblox는 `inst.Destroying:Connect(fn)`). **⚠️ 이 둘은 `native*`의 "미주입이면 조합 폴백" 규칙의 예외다 — 조작이 아니라 판정/훅이라 조합으로 만들 수 없어 미주입이면 명확한 에러**(`addTag`/`setAttribute`와 같은 취급)
├── EngineOps.luau # 주입되는 엔진 op 구현: addTag(inst,{string})/removeTag(inst,{string})=CollectionService, setAttribute(inst,name,v)=inst:SetAttribute(v==nil이면 삭제), nativeDispose(inst)=inst:Destroy()(`dispose(value)`가 `isSlot`이 아닐 때 위임, `base/slot-plan.md`), **[2026-08-21 5라운드 신설, 이름 확정] `native*` 물리 트리 조작 계층** — nativeInsert/nativeExtract/nativeRemove/nativeMove/nativeSwap(0-based 절대 offset + 대상 요소 배열을 받음; Roblox는 offset을 무시하고 배열을 쓰고 DOM은 둘 다 씀). 미주입이면 에러가 아니라 **조합 폴백**. **[2026-08-22 추가] 시간 op 둘** — setTimeout(func, delay) -> Timeout / clearTimeout(t), Roblox는 task.delay/task.cancel로 배선(**인자 순서가 반대라 주의**); `Debounce`/`Throttle`이 얹힐 때 필요하고 그 전엔 미주입이어도 무방(`base/debounce-throttle-plan.md`). **이 줄이 주입 op 전체 목록의 단일 소스다** — 다른 문서는 개수를 세지 말고 여기를 가리킬 것 (`base/dispatch-core-plan.md` "base가 소유하는 핸들러와 주입되는 엔진 op" 절). **[2026-08-24 6라운드 신설] `isInst(value): boolean`**(`H-40` — 요소 타입 검증을 화이트리스트로 뒤집으면서 생긴 판정 술어, quad-roblox는 `typeof(value) == "Instance"`)**와 `onDestroying(inst, fn): Connection`**(`H-11` — `Effect`의 leaf 사망 cleanup을 발화시키는 훅, `bindLifetime``isEffect`일 때 부른다, quad-roblox는 `inst.Destroying:Connect(fn)`). **⚠️ 이 둘은 `native*`의 "미주입이면 조합 폴백" 규칙의 예외다 — 조작이 아니라 판정/훅이라 조합으로 만들 수 없어 미주입이면 명확한 에러**(`addTag`/`setAttribute`와 같은 취급) **[2026-08-28 `Claim`, M5 — `base/claim-plan.md`] `nativeFindChild(inst, key): inst?`** — 매퍼 디스크립터의 키(Roblox는 `Name`, web은 id/selector)로 직계 자식을 찾는 조회 op, quad-roblox는 `inst:FindFirstChild(key)`. 조회라 조합으로 만들 수 없어 `isInst`/`onDestroying`처럼 **조합 폴백의 예외**(미주입이면 명확한 에러 — 이 분류는 에이전트 판단, 사용자 확정은 "필요 핸들을 프로바이더에 남긴다"까지)
├── LifetimeHandle.luau # bindLifetime/canBound/canExecute 실제 구현 — GetPropertyChangedSignal("ClassName") 연결 트릭으로 gcconn 확보, Relate:SetWeak으로 gcconn/gchold 저장(**[정정, 2026-08-18] `SetStrong`이 아님 — 생존은 클로저 upvalue와 `gchold[1]`이 이미 보장, strong으로 잡으면 상호 강참조 누수**, `base/lifecycle-pattern.md`). `canBound`/`canExecute`는 비공개 헬퍼 하나를 공유하는 얇은 진입점(2026-08-14 열한 번째 세션). Relate 자체는 순수 Lua라 quad-roblox 쪽 재구현 없음(quad-base 그대로 재사용)
├── Handlers/
│ ├── Property.luau # 일반 프로퍼티 세팅 + `isTween(realv)` 분기(3-상태 릴레이션 슬롯 `RobloxTween|true|nil`, hasBeenSet 억제, override 정책) — 구 `Handlers/Tween.luau`(높은 우선순위 store-bind 핸들러)는 폐기(`archive/tween-special-bind-key-reversed.md`)

View file

@ -177,6 +177,10 @@ New<<Frame>> "Frame" { ... } -- 직접 사용도 같은 모양
D.Frame = New<<Frame>> "Frame" :: (({ ...타입명시 }) -> Frame)
```
**[2026-08-28 `Claim`]** 그 `{ ...타입명시 }`의 **필드 파트**는 생성기가 `type
<Class>Param`으로 이름 붙여 찍고 `D.Mapper.<Class>`와 공유한다(사용자 확정,
`base/claim-plan.md` §2). children 배열 파트를 어떻게 가르는지는 그 문서 §10-D.
- **이름은 대문자 `New`로 통일**(사용자 확정: *"2. New입니다."*) — PA님 코드
인용의 소문자 `new`와 섞여 있던 것을 정리.
- **뒤집는 게 아니라 명시화**다 — PA님 패턴의 `constructor.Frame =
@ -336,14 +340,20 @@ end
그건 새 메커니즘이었다(`isHandlable` 거부는 `Dispatch.process`의 일반 매치
실패 문구로 떨어지고 그 자리에 특수 분기는 두지 않기로 확정돼 있다) —
**철회**, 일반 문구 그대로. 오해는 사용자 문서가 맡는다.
- **⛔ [2026-08-28 폐기, 10라운드 `H-148``research/existing-mount-plan.md`]
아래 "루트는 사용자가 밖에서 `.Parent =`" 예외는 하루 만에 뒤집혔다** — 사용자:
*"slot 은 물리 장치에 mount 할 방법이 거의 존재하지 않음 … PlayerGui 가
상위에 있고 거기에 GUI 를 여럿 바운딩 해야해서 `Slot { Shop{} … }` 하는게 안
될것 같은 느낌이 듦. 이건 Parent 이상의 문제인것 같아."* 루트는 밖에서
`Parent`를 만지는 게 아니라 **quad가 `Claim`으로 소유**한다(PlayerGui·`Clone()`
사본·Studio GUI — 그 research 문서가 소스, **M5 스코프**(`H-161`)). 그러면 `.Parent =`
사용자가 쓸 자리 자체가 없어진다. 아래는 폐기 전 서술:
- **⭐ [2026-08-28 좁혀서 복원 — `base/claim-plan.md` §5] 아래 "루트는
사용자가 밖에서 `.Parent =`" 예외는 10라운드 `H-148`에서 한때 폐기됐다가 같은 날
`Claim` 갈래 확정에서 복원됐다** — 루트의 `Parent`는 만든 방법(`New`/`Claim`)과
무관하게 어느 부기에도 속하지 않으므로 밖에서 대입해도 된다(사용자: *"밖에서
.Parent 설정하는건 괜찮아. 루트도 quad 소유이긴 한데 … 정확히는 ScreenGUI 가
이미 존재해도 똑같음"*). 여러 스크립트가 한 `PlayerGui`를 쓰는 흔한 경우가 이
경로다. 이미 있는 트리를 quad 소유로 만드는 것은 별개 표면 `Claim`(`base/claim-plan.md`,
**M5 스코프**`H-161`). 아래 원 서술과 그 안의 "만들지 않는다"는 그대로 유효하다.
**[당시 폐기 논거 — 지금은 유효하지 않음]** `H-148`은 사용자의 *"slot 은 물리
장치에 mount 할 방법이 거의 존재하지 않음 … PlayerGui 가 상위에 있고 거기에 GUI 를
여럿 바운딩 해야해서 `Slot { Shop{} … }` 하는게 안 될것 같은 느낌이 듦. 이건
Parent 이상의 문제인것 같아."*에서 출발해 "루트도 `Claim`으로만 소유하고 `.Parent =`
사용자가 쓸 자리 자체가 없어진다"로 갔었다 — 그 뒤 `Claim`이 1회·전체 소유라
여러 스크립트의 PlayerGui를 못 담는다는 게 드러나 위처럼 복원됐다. 아래 원 서술:
**[2026-08-27 확정, 9라운드 `H-146`] 루트는 이 금지의 범위 밖이다 —
quad 트리의 최상위를 quad 밖 부모에 붙이는 건 사용자가 밖에서 `.Parent =`
한다.** 위 인용문의 *"외부에서 직접 Parent 설정해주지 말것"*이 막는 것은

321
.claude/base/claim-plan.md Normal file
View file

@ -0,0 +1,321 @@
# 이미 있는 트리를 quad가 소유하기 — `Claim` + `D.Mapper`
> **[2026-08-28 신설·같은 날 확정 — `research/`에서 승격(옛 파일명 `existing-mount-plan`)]**
> 10라운드 `H-148`(`Parent` 거부 문구)을 논의하다 **루트 마운트 표면의 부재**가
> 드러나 사용자 발의로 만든 문서. 방향(§1~§4)은 `session/2026-08-28-01-handtrace-round10-resolution.md`에서,
> 갈래(§7)는 `session/2026-08-28-02-claim-promotion.md`에서 사용자가 확정했다.
> **M5 스코프**(`H-161` — 프로바이더 op `nativeFindChild`가 필요하니 프로바이더
> 마일스톤이 자연스러운 자리). M2 착수 게이트 아님. 구현 체크리스트는 §9.
>
> **`archive/existing-instance-bind-rejected.md`(2026-08-14 기각)와의 관계**:
> 그 기각은 *"이미 있는 Instance에 나중에 새 props를 다시 바인드"*였고 사유는
> "quad가 만들지 않은 트리의 자식 구성을 바깥이 밀고 당기면 `setLength`/
> `setOffsetSource` 부기가 깨진다"였다. 이 문서는 그 반대 방향 — **한 번
> claim하면 quad가 소유하고 직계 자식은 사용자가 전부 매핑한다**는 계약이라
> claim 뒤엔 quad가 만든 트리와 같은 불변식이 성립한다. **재바인드는 여전히
> 미지원**(claim은 1회, 디스크립터는 `Processed`로 소진).
## 1. 왜 필요한가 (사용자 원문)
`H-146`이 "루트는 사용자가 밖에서 `.Parent =`"로 닫혔는데, 사용자가 이어서
지적했다: *"slot 은 물리 장치에 mount 할 방법이 거의 존재하지 않음. Parent =
처럼 마운트 할 방법이 없는데? 그럼 PlayerGui 가 상위에 있고 거기에 GUI 를
여럿 바운딩 해야해서 `Slot { Shop{} … }` 하는게 안 될것 같은 느낌이 듦. 이건
Parent 이상의 문제인것 같아."* — 즉 **루트가 Slot일 수 없다**(Slot은 quad가
부기를 가진 부모 `inst` 아래에만 산다). 그리고: *"생성할 요소들 자체가 너무
많은 경우 Clone 이 엇청 더 싸서, 그 Clone 된 것 아래 quad 를 바인딩 할 방법이
있으면 좋은것도 사실인듯. … web 에서도 템플릿에 의해 유효한 요소일꺼고,
roblox 에서도 보면서 만들어낸 GUI를 바인딩하는건 흔한 요구라서 이 역시 흔한
필요일꺼야."*
결론(사용자): *"이런 방식으로, 이미 있는 PlayerGui 아래 마운트를 거는거지.
… 이것도 똑같이 Quad 가 소유하게 될 요소가 되는거지."* — quad 밖에서 온
트리(PlayerGui, `Clone()` 사본, Studio에서 만든 GUI)를 **quad 소유**로 만드는
표면이다. 루트의 `.Parent`를 밖에서 만지는 일은 이것과 별개로 **계속 허용**된다
(§5 — 처음엔 "parent 를 설정할 문제 자체가 사라져"로 봤으나 §7-7에서 좁혀 복원).
## 2. 모양
```lua
local M = D.Mapper -- 정의는 D 안에 산다. 유저가 필요하면 꺼낸다
local cloned = Claim(template:Clone(), M.Frame(M.Root) { -- 루트는 이름 대신 센티널
M.TextLabel "Title" { Text = title },
M.Frame "List" {
Slot { … }, -- 기존 부모 아래 Slot — 이제 가능
},
BackgroundColor3 = color, -- props는 New와 같은 derive 테이블
}) -- -> cloned (claim한 루트 Instance — 타입은 넣은 inst의 타입 그대로)
```
- **`D.Mapper.<Class>(key) { … }`는 Instance를 만들지 않고 디스크립터만
만든다**(브랜드 `Mapper`류) — derive 테이블 + 매칭 키. `D.Frame`에 직접 얹지
않는다(사용자: *"D.Frame 에 바로 바인딩은 위험한듯. 의미가 겹쳐버려"*).
`key`는 자식이면 이름(`string`), 루트면 센티널 `D.Mapper.Root`(§7-1).
- **props 타입은 `D.<Class>`와 공유한다.** 생성기가 클래스마다 `type FrameParam
= { … }`를 찍고 `D.Frame``D.Mapper.Frame`이 그 하나를 쓴다 — 리턴만 다르다
(`Frame` vs `MapperDescriptor<Frame>`). 사용자: *"D.Frame 의 함수의 부분들을
type FrameParam = {} 형태로 빼서 공유되는 타입 부분으로 D.Mapper.Frame 도
구성되고, 리턴부분만 다르게"*. `base/bind-system-plan.md`의 `D.Frame =
New<<Frame>> "Frame" :: ((…) -> Frame)` 캐스트가 인라인 타입 대신 이 이름을
쓰게 되는 것뿐이고, `D`가 전량 생성기 산출물이라 손으로 쓸 곳은 없다.
```luau
type FrameParam = { … } -- 생성기 산출물 (필드 파트)
D.Frame :: (FrameParam) -> Frame
D.Mapper.Frame :: (key: string | MapperRoot) -> (FrameParam) -> MapperDescriptor
Claim :: <T>(inst: T, desc: MapperDescriptor) -> T -- T는 inst에서 그대로
```
**⚠️ [2026-08-28 `/code-review`] 배열 파트는 그대로 공유할 수 없다** — `New`
children 배열엔 `MapperDescriptor`가 올 수 없고(오면 런타임 "매치 핸들러 없음"),
매퍼의 배열엔 와야 한다(§2 예시의 `M.TextLabel "Title" {…}`). 사용자 인용은
**필드 파트의 타입 공유**를 승인한 것이고 배열 파트를 어떻게 가를지는 정하지
않았다 → §10-D. `base/bind-system-plan.md``D` 생성기 절엔 아직 `<Class>Param`
없다 — 생성기 구현(`ROADMAP.md` M5 `D/init.luau`)이 이 문서를 같이 본다.
- **`Claim(inst, descriptor) -> inst`가 최상위이고 타입 인자를 받지 않는다.**
사용자: *"Claim 자체는 타입을 받는건 말이 안되어보임. New 와는 완전 다른
계열이라서: New 는 후행 입력에 대한 타입을 선언시키는 D 계열이지만, Claim 은
공유 부분이고 … 엔진 요소를 알 수 없어"*. 반환 타입은 **넣은 `inst`의 타입
그대로**(`<T>(inst: T, …) -> T`) — 처음엔 "디스크립터가 클래스 타입을 실어 `inst`
대조·반환을 좁힌다"(에이전트 제안)로 적었으나 **[2026-08-28 `/code-review`]**
실사용 `inst``template:Clone()`·`PlayerGui` 둘 다 `Instance` 타입이라 그 추론은
성립하지 않는다(좁히려면 사용자가 `::` 캐스트 — `New<<X>>`의 "범위 밖은 `any`"와
같은 취급). 디스크립터 클래스 ↔ `inst` 클래스 대조는 **debug 검사**(§3)의 몫.
`inst`는 quad 밖에서 온 것. 이 호출로 `inst`
매핑된 하위 전부가 **quad 소유**가 된다 — `New`가 만든 것과 같은 gcconn/gchold·부기
(그 셋업을 누가 하는지는 §10-A).
- **디스크립터는 1회용**`PreRef``_fired`처럼 **디스크립터 객체에 소진
플래그**를 세우고 재사용이면 `error(…, 2)`. **[2026-08-28 `/code-review` 정정]**
`PreRef` 관용구의 다른 절반(배열 슬롯을 `ProcessedPreRef` 센티널로 교체)은
**가져오지 않는다** — 매핑 자식의 배열 슬롯은 §4대로 **해석된 Instance로 교체**돼야
`InstanceChildHandler`에 닿고, 루트 디스크립터는 배열에 있지도 않다. 사용자
테이블을 in-place로 바꿀지 새 테이블을 만들지(그러면 `ProcessedModifier` 자리와
인덱스가 `New`와 달라진다)와 Modifier 필드 안에 숨은 디스크립터를 DFS가 보는지는
**구현 시 정할 것**(§9). **같은 `inst`를 두 번 `Claim`하는 것도 error**(§7-7) —
어느 레지스트리로 판정하는지는 §10-B.
- **매칭은 프로바이더 주입 op** `nativeFindChild(inst, key)`(가칭) — Roblox는
`Name`, web은 id/selector. **quad-base가 순회·부기 전반을 구현하고
프로바이더는 이 핸들만 낸다**(사용자: *"quad-base 에서 전반을 구현해주고
필요 핸들을 구현하라고 남기는건 괜찮은 생각"*). 주입 op 전체 목록의 단일
소스는 `base/architecture.md`의 소스 트리(`EngineOps.luau` 줄) — 거기 추가한다.
- **여러 quad 인스턴스가 한 트리를 claim — UB**(사용자 확정). 같은 quad의 이중
claim은 위처럼 error.
- **`H-142`(props에 `Parent` 금지)는 그대로.** 매퍼 디스크립터의 props도 `New`
같은 derive 테이블이라 같은 금지를 받는다.
## 3. 계약 — 자식은 전부 매핑한다
사용자: *"모든 개체를 유저가 직접 네임을 매핑해서 derive 테이블 안에서 내부
요소를 전부 매핑해준다를 계약으로 잡으면 문제가 없다고 생각해."*
- **부기 대상(그려지는 자식)은 전부 매핑해야 한다.** 안 된 자식이 남으면
`nativeInsert`의 삽입 위치(web은 곧 DOM 순서)와 Length/Offset이 어긋난다.
- **디스크립터 배열 순서가 정본이다**(§7-2 (a)). 기존 트리의 실제 순서가
다르면 일치는 사용자 책임 — quad는 `nativeMove`로 맞추지 않는다. Roblox는
물리 순서가 의미 없어 비용 0, web에선 어긋나면 UB(debug 검사 대상).
- **숏핸드(`UICorner` 등 `UI*`)는 부기 대상이 아니다** — 그려지지 않고 Roblox에만
있으며 단순 `Parent` 대입 요소. 사용자 확정: *"숏핸드를 quad 에서만 직접
쓰거나 … 아니면 실제 UI 객체를 바인딩해서 숏핸드를 안 쓰거나"* — 둘 중 하나:
(i) 템플릿엔 `UI*`가 없고 quad가 숏핸드 키로 만든다, (ii) 템플릿의 `UI*`
`M.UICorner "UICorner" {…}`처럼 **실제 객체로 매핑**하고 그 부모에 숏핸드
키는 안 쓴다. 섞으면(템플릿에 `UICorner`가 있는데 숏핸드 키도 씀) 둘이
생기는 것은 UB.
- **이름 중복·부재는 UB**(사용자 확정). **debug 모드**에선 `seen` 맵으로 중복을
잡아 error(사용자 제안). 검사 범위를 어디까지 넓힐지(부재·클래스 불일치·
미매핑 부기 대상·물리 순서 불일치·이중 claim)는 **디버깅 도구 설계의 몫**으로
옮겼다(§7-4 → `research/debug-tooling-plan.md`의 "열린 질문" 절) — 여기선
"debug 검사가 있다"까지만 확정.
## 4. 처리 순서 — `drive` 위의 한 겹
**`New`와 반대 방향에서 시작한다.** `New`는 안쪽 생성자가 먼저 평가돼 자연히
bottom-up이지만, 매퍼의 안쪽은 평가 시점에 자기 Instance를 모른다(부모가
아직 없다). 그래서 `Claim`이 **DFS로 내려가며 이름으로 해석 → 자식부터
`drive` → 올라오며 부모 `drive`**. 사용자: *"핸들러가 된다면 위험해. 일반
생성과 다르게, 상위 부터 처리하거든 … DFS 로써, 내려가는게 먼저고 그 뒤에서
derive 를 걸어야해. 이건 derive 에선 구현하지 않고, 그 위의 무언가로써
구현되어야할듯."* — `drive`/핸들러 층은 안 바뀌고 그 **위의 한 겹**이다.
- 매핑된 정적 자식은 부모의 derive 테이블에서 **평범한 정적 자식**(배열 자리의
Instance)으로 보인다 — 해석이 끝난 뒤엔 `InstanceChildHandler`가 그대로 받아
`setOffsetSource(inst, k, None)``v.Parent = inst``setLength(inst, k, 1, inst)`
(`base/dispatch-core-plan.md`의 `H-134` 문단). **별도 핸들러는 없다**(§7-8) —
이미 거기 있는 자식에 같은 `Parent`를 재대입하는 것은 엔진 no-op이고, `H-154`
문단이 이미 *"`Parent = inst`(같은 값, 엔진 no-op)"*으로 전제한 사실.
- Slot은 평소처럼 `native*`로 기존 부모 아래 끼운다.
- **⚠️ [2026-08-28 `/code-review`] `PreRef`/`OnCreated`의 불변식이 약해진다.**
`drive`를 그대로 쓰므로 claim된 inst에서도 `PreRef`가 먼저 발화하지만, `New`
보장하던 *"아직 자식도 프로퍼티도 없다"*(`base/bind-system-plan.md`)·*"이 인스턴스에
뭐가 됐든 일어나기 전"*(`base/lifecycle-hooks-plan.md`)은 **거짓**이다 — inst는 이미
템플릿의 자식·프로퍼티를 갖고 있고, 매핑 자식의 `ChildAdded`는 아예 안 뜬다.
`Claim`에서 `PreRef`가 뜻하는 것은 "quad가 이 inst에 무언가 하기 전"뿐. §4가
특수 분기를 금지하므로 지키려 하지 않고 **문서화 대상**(§9)으로 둔다.
## 5. 루트의 `Parent`는 부기 밖이다 — 밖에서 `.Parent =` 허용
**`H-146`의 루트 예외는 좁혀서 복원된다**(§7-7). 사용자: *"밖에서 .Parent
설정하는건 괜찮아. 루트도 quad 소유이긴 한데, .Parent 를 밖에서 설정하는건
괜찮음. 정확히는 ScreenGUI 가 이미 존재해도 똑같음."*
- **quad 트리의 루트**(`New`로 만든 것이든 `Claim`한 것이든)의 `.Parent`
어느 Length/형제 순서 부기에도 속하지 않는다 — 그래서 사용자가 밖에서
`root.Parent = PlayerGui`로 붙이고 떼는 것은 **허용**이고, `H-146`의 사용자
논거(*"부기가 없는 객체에 Quad 의 객체를 주입하는 성격의 API 는 아니거든 …
해당 부분은 각 엔진을 사용하는 최종 사용자의 몫"*)가 그대로 성립한다.
`Mount(root, parent)`류 표면은 여전히 만들지 않는다.
- **금지는 그대로 "quad가 소유한 부모 *아래*"다** — Slot 요소·정적 자식 자리에
밖에서 끼우거나 빼는 것(`base/slot-plan.md`의 "동적 자식은 반드시" 절). 루트의
부모는 quad가 소유하지 않은 것이라 이 금지 밖이다.
- **props의 `Parent`는 여전히 금지**(`H-142`) — 붙이는 건 props가 아니라 밖의 한 줄.
거부 배선의 에러 문구는 일반 매치 실패 그대로(`H-148`), 오해는 사용자 문서가 맡는다.
- **여러 스크립트가 한 `PlayerGui`를 쓰는 흔한 경우는 이걸로 닫힌다** — 각
스크립트가 자기 `ScreenGui``New`(또는 `Claim`)하고 `.Parent = PlayerGui`
(이 경로는 PlayerGui를 claim하지 않으므로 §6/§10-C의 한계와 무관).
`Claim(PlayerGui, …)`은 **PlayerGui 전체를 그 스크립트가 소유하겠다**는 뜻이라
한 번만 가능하고, 여러 스크립트가 PlayerGui 직하 `Slot`을 공유해야 하면
**`Claim`을 한 번 하고 그 안에 Slot을 만들어 반환하는 중간 모듈**을 둔다
(사용자: *"정확히는 두번 Claim 불가하다는 의미. 필요하다면 Slot 을 안에 만들고
리턴하는 중간 모듈을 만들어야함"*) — 단 PlayerGui 자체를 claim하는 그 경우는
§6/§10-C(엔진이 자식을 넣는 컨테이너)의 답에 걸린다.
## 6. 이 문서가 여는 것
- **루트 컨테이너**: `Claim(PlayerGui, M.PlayerGui(M.Root) { Slot {…} })` — PlayerGui가
quad 소유 부모가 되어 Slot이 그 아래 산다(한 스크립트가 PlayerGui 전체를
소유하는 경우). **⚠️ [2026-08-28 `/code-review`] 이건 own-all 계약(§3)의 한계
사례다** — Roblox `PlayerGui`는 엔진이 `StarterGui`를 스폰·리스폰마다 새로 복제해
넣는 컨테이너라(`ResetOnSpawn`), 디스크립터가 매핑할 수 없는 자식이 **quad 소유
부모 아래 밖에서** 들어온다(`base/slot-plan.md`의 "동적 자식은 반드시" 절이 UB로
못 박은 그것). 계약 그대로면 *"엔진이 자식을 넣는 컨테이너를 claim하는 것은 UB"*
이고 흔한 경로는 §5의 `.Parent =`다 — 이 한계를 계약으로 명문화할지, `StarterGui`
안 쓰는 전제를 문서화로 둘지는 **§10-C**. 같은 자리: 매퍼 생성기 범위(GUI 클래스)에
`PlayerGui`류 컨테이너가 들어가는지도 거기서.
- **템플릿 대량 생성**: `template:Clone()``Claim` — 각 사본이 독립 소유.
Claim이 Instance를 돌려주므로 **Slot 요소로도 그대로 쓸 수 있다**(요소는
`inst`) — "요소가 너무 많은 경우"의 답.
- **비루트 사용**: `New "Frame" { Claim(clone, …) }` — 반환된 `inst`가 정적
자식으로 들어가면 `InstanceChildHandler``Parent =`와 부기를 한다. 평가
순서상 `Claim`이 먼저 끝나므로 bottom-up이 유지된다.
- **claim된 부모 안의 `New` 자식**: `M.Frame "List" { New "Frame" {…} }` — 매핑
(이미 있음)과 생성(새로 붙임)이 한 배열에 섞여도 된다(§7-3). `New` 자식은
디스크립터 테이블이 평가될 때 이미 다 구워져(자기 서브트리 `drive` 완료) 있고,
`Claim`이 올라오며 부모를 `drive`할 때 정적 자식으로 부기된다 —
`New "Frame" { New "Frame" {} }`과 같은 모양. **위치는 프로바이더의 몫**:
Roblox는 `.Parent =`라 순서가 무의미하고, **web은 정적 자식 핸들러가
`nativeInsert(offset)`을 써야 디스크립터 순서 자리에 놓인다** — 안 그러면 맨 뒤
(그리고 **이미 붙어 있는 매핑 자식**엔 그 핸들러가 `nativeInsert`를 다시 부르면
안 된다 — 같은 부모 안 재삽입은 이동이라 §3 "quad는 `nativeMove`로 맞추지 않는다"와
어긋난다; web 정적 자식 핸들러는 "이미 그 부모의 자식이면 건너뜀"을 가져야 한다)
(사용자: *"Add 가 위치를 진짜 실어서 보내지 않으면 맨 뒤에 놓인다는게 문제일
뿐"*). §3 "디스크립터 순서가 정본"의 따름정리이고 `Claim`의 결정이 아니다.
## 7. 결정 기록 — `research/` 시절 §5 갈래의 답 (2026-08-28)
당시 갈래 목록(a/b/c 선택지 원문)은 `session/2026-08-28-02-claim-promotion.md`
끝의 "옛 §5 원문" 절에 전문 보존. 번호는 그 research 문서의 §5 번호 그대로.
1. **루트 디스크립터의 키 — (a) 센티널.** 갈래 (b)("클래스 없는 맨 테이블 +
`Claim<<"Frame">>`", 에이전트 권고)는 **기각** — 사용자: *"권고 b는 문제가
생겨. 루트에 대해서 {} 안의 타입체크와 타입 자동완성이 전혀 안 먹음."* 그리고
`Claim`이 타입 인자를 받는 것 자체가 `New` 계열과 어울리지 않는다(§2 인용).
(c)("이름을 받되 무시")도 안 씀. 센티널은 사용자 스케치 `MapperRoot = {}
Mapper.Frame (MapperRoot) {}` 그대로이고, **놓는 자리 `D.Mapper.Root`
에이전트 제안**(매퍼 옆에 두면 `M.Frame(M.Root)`로 읽힌다) — 이름만 바뀔 수
있는 항목. 자식 디스크립터에 센티널을 주는 것(`M.Frame(M.Root)`가 루트가
아닌 자리에)은 debug 검사 후보.
2. **물리 순서 — (a) 디스크립터 순서가 정본, 일치는 사용자 책임.** 사용자:
*"나는 처음에 A 를 생각했어. 권고 그대로 가줘."* (b)(`nativeMove`로 quad가
맞춤)는 기각.
3. **claim된 부모 안의 `New` 자식 — 허용.** 사용자: *"새로 붙임 자체는 한 배열에
섞이는게 문제는 없어보여. 그 경우에서도 순차 마운트 Add 는 작동할것이거든."*
지적한 순서·위치 문제는 §6 마지막 항목(프로바이더 요구사항)으로.
4. **debug 검사의 범위 — 여기서 정하지 않고 `research/debug-tooling-plan.md`
이동.** 사용자: *"디버깅 도구 만들 때 고려해야할 점으로 옮겨져야해. 부분 부분
디버깅 가능성을 아직 다 논한게 없어서 지금 그림으로 보면 작은 그림을 먼저
그리는거라서, 미결상황으로, 위치 이동이 필요함"*. 이 문서엔 "debug 검사가
있다"(§3)만 남는다.
5. **표면 이름 — `Claim` + `D.Mapper`.** 사용자: *"표면 이름은 Claim 이 가장
마음에 들어. D.Mapper 가 이미 있는걸 매핑해서 내가 가진다는 의미적으로 가장
맞고."* `Mount`/`Adopt`/`D.Existing` 기각.
6. **마일스톤 — M5**(`H-161`, 헤더).
7. **여러 스크립트가 한 `PlayerGui` — (α) `Claim`은 1회·전체 소유, 루트의
`.Parent =`는 밖에서 허용.** 문항 원문은 "여러 스크립트/여러 quad"를 나란히
놓았지만 실제 흔한 경우는 **한 quad·여러 스크립트**(같은 `quad` 모듈을
require — 다중 quad UB가 아니라 이중 claim error에 걸린다)라, 그 사례를 막는 게
진짜 문제였다. 갈래 (a)(루트 컨테이너용
별도 표면 — `H-146` 인용문이 정확히 반대한 것) / (b)(부분 매핑 모드 — web에서
offset이 남의 자식을 못 봐 `Claim`의 의미가 엔진 의존이 됨) / (c)(다중 claim,
각자 자기 자식만 소유 — (b)와 같은 약화) 전부 기각. 확정은 §5 — 사용자 원문
그대로 *"정확히는 두번 Claim 불가하다는 의미. 필요하다면 Slot 을 안에 만들고
리턴하는 중간 모듈을 만들어야함. 밖에서 .Parent 설정하는건 괜찮아. 루트도
quad 소유이긴 한데, .Parent 를 밖에서 설정하는건 괜찮음. 정확히는 ScreenGUI 가
이미 존재해도 똑같음."* 마지막 문장이 `Claim`한 루트에도 같은 허용을 준다 —
루트의 `Parent`는 만든 방법과 무관하게 부기 밖.
8. **매핑된 정적 자식의 `Parent` 대입 — 같은 핸들러, 재대입 감수.** 사용자:
*"5-8 확인완료."* 근거는 §4.
## 8. 검토 후 안 만들기로 한 것
- `Claim<<"Frame">>` 타입 인자 / 루트를 맨 테이블로(§7-1).
- 루트 컨테이너용 별도 얇은 표면, `Claim`의 부분 매핑 모드, 다중 claim(§7-7).
- claim 시 `nativeMove`로 물리 순서를 디스크립터에 맞추기(§7-2).
- 매핑된 자식 전용 핸들러(§7-8).
- `Mount(root, parent)`류 표면(`H-146`, §5).
- 이미 있는 Instance에 나중에 props를 재바인드(`archive/existing-instance-bind-rejected.md`,
헤더).
## 9. 구현 체크리스트 (M5) · 문서화 대상
- `D.Mapper.<Class>` 생성기 산출 + `type <Class>Param` 필드 파트 공유(사용자 확정;
배열 파트는 §10-D) + 루트 센티널(사용자 확정 — 놓는 자리 `D.Mapper.Root`
에이전트 제안) + 디스크립터 브랜드(`MapperDescriptor`는 가칭 — `Brand` 인스턴스
브랜드로 만든다는 것은 `base/brand-plan.md`의 일반 규칙이지 새 결정 아님).
- `Claim(inst, desc) -> inst` — DFS 해석 → bottom-up `drive`, 소진 플래그(§2), 같은
`inst` 이중 claim error(레지스트리는 §10-B), 이미 quad 소유인 inst의 gcconn/gchold
셋업(§10-A). **구현 시 정할 것**: 사용자 테이블 in-place 교체 vs 새 테이블,
Modifier 안의 디스크립터 처리, 패키지 안 정의 파일 위치(`quad-base/src/Claim.luau`
가칭 — `base/architecture.md` 소스 트리에 반영은 M5 착수 때).
- 프로바이더 op `nativeFindChild(inst, key)`(이름 가칭) — `base/architecture.md` 주입 op
목록에 추가됨(quad-roblox는 `inst:FindFirstChild(key)`). "`native*` 조합 폴백의
예외 — 조회라 조합으로 만들 수 없어 `isInst`처럼 미주입이면 명확한 error"는
**에이전트 분류**(사용자 발언은 "필요 핸들을 구현하라고 남기는 건 괜찮다"까지).
- debug 모드 `seen` 맵(범위는 `research/debug-tooling-plan.md`가 소스).
- `ROADMAP.md` M5 체크박스가 진행의 소스.
- **문서화 대상**(`research/documentation-content-map.md` §4): "전부 매핑" 계약과
숏핸드 (i)/(ii) 규칙, 루트 `.Parent =`는 밖에서 / 그 아래는 절대 직접 하지 말 것,
여러 스크립트의 PlayerGui는 각자 `ScreenGui` + 중간 모듈 패턴, claim된 inst에선
`PreRef`/`OnCreated`가 "이미 있는 것 위에서" 뜬다는 것(§4).
## 10. 사용자 판단 필요 — M5 착수 전 (2026-08-28 `/code-review high`가 낸 공백)
전부 **새 메커니즘이 필요한 공백**이라 `conventions.md` 규칙대로 결정 없이 본문에
넣지 않았다. M2 게이트 아님. `question.md`에도 같은 넷이 올라가 있다.
- **A. claim한 inst의 gcconn/gchold 셋업 자리.** §2는 *"`New`가 만든 것과 같은
gcconn/gchold·부기"*를 약속하는데, 그 (0) 셋업은 quad-roblox `New` ②단계의
**인라인 코드**(`base/bind-system-plan.md`의 `New` 의사코드 ② 단계, `lifecycle-pattern.md`
(0))이고 `Claim`은 quad-base다. §4대로 `drive`만 부르면 claim된 루트 아래 첫
`Slot``bindLifetime``gchold[value] = true`에서 nil 인덱스로 죽는다. 갈래:
(a) 프로바이더가 (0) 셋업을 op로 노출(`nativeAdopt(inst)` 가칭)하고 `Claim`
해석한 inst마다 부른다 / (b) `Claim` 본체를 프로바이더에 둔다(quad-base는 순회
알고리즘만) / (c) (0) 셋업 자체를 quad-base 함수로 옮기고 `New`도 그걸 쓴다.
**권고 (a)**`nativeFindChild`와 같은 모양이고 `New`의 경로가 안 바뀐다.
- **B. 같은 `inst` 이중 claim의 판정 레지스트리.** 기존 소유권 레지스트리
`elementOwner`(`base/slot-plan.md`)에 기록하면 `claimOwner`가 §6의 *"claim한
inst를 Slot 요소/정적 자식으로 그대로 쓴다"*를 error로 죽인다. 갈래: (a) (0)
셋업이 만드는 per-inst `InstData``claimed` 플래그 — 셋업과 같은 자리라 새
Relate가 없다 / (b) 별도 weak-key 레지스트리. **권고 (a)** — A의 답에 따라
자동으로 정해지는 모양.
- **C. 엔진이 자식을 넣는 컨테이너(`PlayerGui`)와 own-all 계약.** §6 첫 항목. 갈래:
(a) 계약에 *"엔진이 자식을 넣는 컨테이너를 claim하는 것은 UB"*를 명문화하고
흔한 경로는 `.Parent =`(§5)로 — `StarterGui`를 안 쓰는 프로젝트만 PlayerGui를
claim / (b) 컨테이너용 부분 매핑 — §7-7에서 이미 기각. **권고 (a)**. 부수: 매퍼
생성기 범위에 `PlayerGui`류 컨테이너를 넣을지(`base/bind-system-plan.md`의 생성 범위
*"GUI에 쓰이는 모든 인스턴스"*엔 없다).
- **D. `type <Class>Param`의 배열 파트.** 필드 파트 공유는 확정(§2). children
배열의 원소 유니언은 `New`(Instance·Slot·State…)와 매퍼(+ `MapperDescriptor`)가
달라야 한다. 갈래: (a) `type FrameParam<C> = { [number]: C, …필드 }`처럼 원소
타입을 파라미터로 — 생성기 한 줄 / (b) 필드 파트만 `FrameFields`로 빼고 배열은
각자 — 타입 둘. **권고 (a)**. 어느 쪽이든 `luau-analyze` 스파이크로 확인
(`base/typing-limits.md` 설계 체크리스트 6번 — *"추론만으로 … 확정하지 말 것"*).

View file

@ -131,6 +131,8 @@ value)`류 base 유틸을 문서가 `inst`라고만 부르는 것과 같은 관
호이스팅되어 fire, 즉 "이 인스턴스에 뭐가 됐든 일어나기 전"에 콜백이
불림. 새 Dispatch 메커니즘 불필요, `PreRef` 그대로 재사용.
**[2026-08-28 `Claim` 캐비엇]** `Claim`(`base/claim-plan.md` §4)한 inst에선 이 보장이 약해진다 — inst는 이미 템플릿의 자식·프로퍼티를 갖고 있고 `OnCreated`가 뜻하는 건 "quad가 손대기 전"뿐이다.
**v1과의 관계 — 이름이 같아 보여도 메커니즘은 다름.** `base/ref-plan.md`
"`phase` 옵션 폐기 → 위치로 표현, `PreRef` 신설" 절에 이미 이렇게 확정돼
있음:

View file

@ -115,7 +115,10 @@ base는 여전히 `T`가 뭔지 모른다 — **아는 건 백엔드고 base는
quad 자신의 배관은 `Destroying` 하나만 보므로 안 깨진다 — 영향은
사용자 코드/렌더 쪽이다.
- `isInst`는 이 조합 폴백의 예외다(위 문단) — 판정이라 조합으로 만들 수
없고, 미주입이면 명확한 에러여야 한다.
없고, 미주입이면 명확한 에러여야 한다. 같은 예외가 **`onDestroying`**(`H-11`,
훅)과 **[2026-08-28] `nativeFindChild`**(`Claim`의 조회 op, `base/claim-plan.md`
예외 분류는 에이전트 판단)에도 적용된다. 전체 목록의 소스는
`base/architecture.md`의 소스 트리(`EngineOps.luau` 줄).
- **⚠️ 전제 — 한 Slot의 물리 자식은 부모 안에서 연속 구간을 차지한다.** 범위 op이
성립하는 근거가 전부 이것이다(offset이 누적합이고 중첩 Slot도 같은
`physicalTarget`을 공유하므로 구조적으로 참). quad 밖에서 그 부모에 자식을 끼워
@ -234,8 +237,8 @@ Slot에 들어간 요소는 **ownership이 귀속**되며 다른 곳에 마운
별다른 강제를 안 했지만(`reference/quad-v1-architecture.md`의 mount.lua 분석 참고 —
실제로는 부모/자식 부기까지 했지만 다중 마운트 방지는 없었음), v2는 **Slot의
마운트 경로 자체**(`attachSlot` 분해분 — 별도 `Mount` 함수가 아니다; **[2026-08-28]**
이미 있는 트리를 quad가 소유하는 `Claim``research/existing-mount-plan.md`에서
논의 중이고 그것도 이 단일 마운트 불변식을 그대로 진다)가 이 강제를 담당.
이미 있는 트리를 quad가 소유하는 `Claim`(`base/claim-plan.md`, 같은 `inst` 이중
claim은 error)도 이 단일 마운트 불변식을 그대로 진다)가 이 강제를 담당.
Fusion의 `Children` SpecialKey는 이걸 "특정 SpecialKey 하나의 내부 부기"로만
구현했고(재사용 가능한 1급 프리미티브가 아님), Vide는 아예 이 개념이 없어서
@ -2152,11 +2155,14 @@ UI에 직접 관측, (2) `Dispatch.setLength(inst, i, slot.Length)`가 형제
정확히 호출하는 유일한 정당 경로라, 이걸 우회해서(예: 외부 코드가 Slot이
마운트해둔 부모 Instance에 직접 `.Parent = parentInst`로 자식을 끼워
넣는 것) 자식을 추가/제거하면 `Length`/형제 순서 계산이 그 변화를 몰라
조용히 어긋남 — 별도 방어 로직 없음, 문서 경고로만 남김. **[2026-08-28 10라운드
`H-148`]** 루트(`PlayerGui` 등 quad 밖 부모)는 사용자가 `.Parent =`로 붙이는 게
아니라 **quad가 `Claim`으로 소유**하는 쪽으로 방향이 확정됐다
(`research/existing-mount-plan.md`, M5 스코프 — `H-161`) — 그래서 이 금지에 예외가 없어진다.
(2026-08-27에 하루 있었던 "루트는 밖에서" 예외는 폐기.)
조용히 어긋남 — 별도 방어 로직 없음, 문서 경고로만 남김. **[2026-08-28 확정]** 이 금지는
**quad가 소유한 부모 *아래***에만 걸린다 — **루트**(quad 트리의 최상위, `New`로 만든
것이든 `Claim`한 것이든)의 `.Parent`는 어느 부기에도 속하지 않으므로 사용자가 밖에서
`root.Parent = PlayerGui`로 붙이고 떼는 것은 허용이다(`base/claim-plan.md` §5 —
10라운드 `H-148`에서 한때 "루트도 `Claim`으로만"으로 폐기됐다가 같은 날 좁혀
복원, 사용자: *"밖에서 .Parent 설정하는건 괜찮아"*). 이미 있는 트리(PlayerGui
전체·`Clone()` 사본)를 quad 소유 부모로 만들어 그 아래 Slot을 두는 것은 별개 표면
`Claim`(M5 스코프 — `H-161`).
## `Slot:Single(state, updateFn?, opts?)` — 확정 (2026-08-11 세션, `:List` 위의 순수 sugar)

View file

@ -27,7 +27,7 @@ M3=반응형)다. 그 교체의 부작용으로 한때 `question.md` 최우선
(소스는 `qa-request/pre-implementation-handtrace-round8-followup.md`),
`question.md` 최우선 절은 다시 비어 있다. **[2026-08-27] 9라운드**(그 커밋
`9dd8213`의 델타 재트레이싱, 발견 `H-124`~`H-141`)는 **Q1~Q3가 `base/`
반영됐고**, 같은 날 Q4~Q10·`H-138`·`H-139`·`H-142`까지 **전량 반영** — 소스는 `-round9-followup.md`. 그 반영분에 `/code-review high`가 낸 새 메커니즘 넷(`H-143`~`H-146`)도 **같은 날 `session/2026-08-27-03-handtrace-round9-h143-h146.md`에서 전부 권고 (a)로 확정·반영**돼 **[2026-08-28] 10라운드**(`-round10.md`, 광범위 탐사 `H-150`~`H-157` 포함)도 같은 날 전량 결정·반영(소스 `-round10-followup.md`) — 둘이 뒤집혔다(`fn`은 자기 구독을 못 바꿈 / 루트는 `Claim`으로 quad 소유, `research/existing-mount-plan.md`). 같은 날 후속 `H-158`~`H-164`(`EmitReceive`·`_catchUp` 포함)까지 반영. 남은 미결은 `question.md` 최우선 절의 `Claim` 갈래 하나(게이트 아님). 저장소 루트에
반영됐고**, 같은 날 Q4~Q10·`H-138`·`H-139`·`H-142`까지 **전량 반영** — 소스는 `-round9-followup.md`. 그 반영분에 `/code-review high`가 낸 새 메커니즘 넷(`H-143`~`H-146`)도 **같은 날 `session/2026-08-27-03-handtrace-round9-h143-h146.md`에서 전부 권고 (a)로 확정·반영**돼 **[2026-08-28] 10라운드**(`-round10.md`, 광범위 탐사 `H-150`~`H-157` 포함)도 같은 날 전량 결정·반영(소스 `-round10-followup.md`) — 둘이 뒤집혔다(`fn`은 자기 구독을 못 바꿈 / 루트는 `Claim`으로 quad 소유, `base/claim-plan.md` — 다만 루트의 `.Parent =`는 같은 날 밖에서 허용으로 복원). 같은 날 후속 `H-158`~`H-164`(`EmitReceive`·`_catchUp` 포함)까지 반영. 같은 날 `Claim` 갈래까지 전량 확정돼 `base/claim-plan.md`로 승격 — `question.md` 최우선 절은 다시 비어 있다. 저장소 루트에
`quad-base/src/`(`New()`/`RunInit`/`AddPlugin`/`Relate`/`Debug`)/
`quad-types/src/`/`type-version-check/src/`가 실제로 존재(`quad-roblox/src`는
아직 빈 폴더 — M5에서 채워짐), 자세한 진행 상황은 루트 `ROADMAP.md`

View file

@ -10,7 +10,7 @@
| 문항 | 무엇 | 상태 |
|---|---|---|
| `H-147` | 죽은 핸들에서 `Rerun()` | ✅ **확정 — 문항의 전제를 뒤집음**: `fn`/cleanup은 자기 생명주기를 못 바꾼다(A). `H-143`도 함께 소멸 |
| `H-148` | `Parent` 거부 문구 | ✅ **전제 정정** — 문구가 아니라 루트 마운트 표면의 부재. `research/existing-mount-plan.md` 신설(`Claim` + `D.Mapper`), `H-146` 루트 예외 폐기, 전용 문구 철회 |
| `H-148` | `Parent` 거부 문구 | ✅ **전제 정정** — 문구가 아니라 루트 마운트 표면의 부재. `base/claim-plan.md` 신설(`Claim` + `D.Mapper`), `H-146` 루트 예외 폐기(**→ 같은 날 후속 3에서 좁혀 복원** — 루트의 `.Parent =`는 밖에서 허용, 아래 마지막 절), 전용 문구 철회 |
| `H-149` | Observer `Subscribe` 위임과 `level 2` | ✅ **확정 (a)**`Subscribe`/`Unsubscribe`도 게이트·등록을 인라인, 위임 없음 |
| `H-150` | `Effect._blocker` 죽은 부품 | ✅ **확정 (a)** — 제거, 억제는 Effect 핸들의 `canExecute` |
| `H-151` | 게이트 우회 계약 | ✅ **확정 — (a) 문서화 + `Refresh` 캐치업 폐기**: Effect의 `_epochs`는 emit 수신 때만 갱신, 재바인드/재구독은 초기 설치와 같다 |
@ -20,7 +20,7 @@
| `H-163` | Slot 내부 Observer × 홀드 발화 | ✅ **확정 (a)** `_listObserver`는 트리 확정 뒤(`materializeSlotTree` 꼬리)에 끄고 묶고, 홀드가 있었으면 reconcile 1회; `_baseObserver`는 끄고 묶음 + **`EmitReceive` 인터페이스**(전파 루프 계층 분리) |
| `H-164` | 홀드 발화의 `emitFrom == nil` | ✅ **전제 정정 (c)**`nil` = 출처 없음(설치 또는 캐치업), `from` 보관 기각 |
| `H-160` | `Destroying` 경로 cleanup `Rerun` | ✅ **확정 (a) → `H-159`로 정정**: `rawRerun``_cleanupRunning`이면 **버리지 않고 `_rerunRequired`로 홀드** + "error 나면 그 Effect는 죽는다" 계약 |
| `H-161` | M5 루트 부착·다중 스크립트 `Claim` | ✅ **확정 (a)** `Claim`을 M5 스코프로; §5-7 다중 스크립트는 미결 |
| `H-161` | M5 루트 부착·다중 스크립트 `Claim` | ✅ **확정 (a)** `Claim`을 M5 스코프로; §5-7 다중 스크립트는 미결**같은 날 후속 3에서 확정**(`Claim` 1회·전체 소유 + 루트의 `.Parent =`는 밖에서 허용, `base/claim-plan.md` §7-7) |
| `H-153` | Store 예약 이름 런타임 가드 | ✅ **확정 (a)** — 생성자·`Of(name)`에 예약 이름 검사(level 2), 그림자 = store 자신 (I) |
| `H-154` | `InstanceChildHandler` dedup | ✅ **확정 (a)** — retractor 첫 줄 `if nextValue == v then return end` |
| `H-152`/`H-155`~`H-157` | 갈래 없음 | ✅ 반영(`gate-plan.md` 조립 첫 줄 `StateBrand:register` / `ROADMAP.md` M6×3·M11 / `debounce-throttle-plan.md` 7절 `H-32` 문단 / `store-plan.md` 빈 Store 실측 완료) |
@ -92,7 +92,7 @@
거기에 GUI 를 여럿 바운딩 해야해서 `Slot { Shop{} … }` 하는게 안 될것 같은
느낌이 듦. 이건 Parent 이상의 문제인것 같아."* → 이미 있는 트리를 quad가
소유하는 `Claim(inst, D.Mapper.<Class> "Name" {…})` 제안(원문·확정·갈래 전량은
`research/existing-mount-plan.md`).
`base/claim-plan.md`).
**확정된 것**: 방향 자체 / 디스크립터는 `D.Mapper`에(`D.Frame`에 직접 얹지 않음)
/ `Claim` DFS(내려가며 해석 → 자식부터 `drive`)는 derive 위의 한 겹 / 부기 대상
@ -100,8 +100,11 @@
매핑하거나 / 이름 중복·부재는 UB + debug 모드 `seen` 검사 / `nativeFindChild`
프로바이더 op, 순회는 quad-base / 다중 quad 한 트리 UB / M5 스코프(`H-161`로 당김).
**따름**: 전용 문구 **철회**(일반 매치 실패 그대로), `H-146` (a)의 "루트는 밖에서
`.Parent =`" **폐기**(루트가 quad 소유). `H-142` 키 금지는 그대로.
**미결**(개수는 그 research 문서 §5가 소스)은 다음 배치 문항.
`.Parent =`" **폐기**(루트가 quad 소유) — **[당시 결정, 같은 날 후속 3에서 좁혀
복원됨]** 루트의 `Parent`는 부기 밖이라 밖에서 대입 허용(`base/claim-plan.md` §5).
`H-142` 키 금지는 그대로.
**미결**이던 갈래 여덟은 같은 날 전량 확정(이 파일 마지막 절, 결정 기록은
`base/claim-plan.md` §7).
## `H-149` — Observer `Subscribe`/`Unsubscribe`도 인라인 (a)
@ -221,7 +224,7 @@ then return end`(`SlotHandler` 동형). 같은 값 재발행에 `Parent = nil
`-round9-followup.md`/`-round9.md`의 `H-143`/`H-144`/`H-146` 소멸·정정 배너.
**남은 미결**: `H-158`(`state:Block` 슈가 폐기 → `state:Apply(blocker)`, 권고만) —
`question.md`에; `research/existing-mount-plan.md` §5 갈래 — 다음 배치. (이후 `H-158`은 확정, 아래 절.)
`question.md`에; `base/claim-plan.md` §5 갈래 — 다음 배치. (이후 `H-158`은 확정, 아래 절.)
## 감사 루프 (2026-08-28, 10라운드 반영분)
@ -281,11 +284,11 @@ cleanupRunning 플래그가 풀리지 않는 문제가 날듯. 모든 재 진입
계약으로 상향되어도 문제는 없는듯. 이미 _running 도 그러한 제약을 받으니까."* —
`effect-plan.md` "error 시 UB" bullet을 **"그 Effect는 죽는다"** 계약으로.
## `H-161``Claim`을 M5 스코프로 (a); §5-7은 미결
## `H-161``Claim`을 M5 스코프로 (a); §5-7은 미결 → 같은 날 후속 3에서 확정
**사용자 확정**: *"H-161 는 확인. M5 스코프로 올라가도 될것으로 보임."*`ROADMAP.md`
M5 체크박스·`research/existing-mount-plan.md` 헤더·§5-6. 다중 스크립트/루트 컨테이너
(§5-7)는 답이 없어 미결 유지.
M5 체크박스·`base/claim-plan.md` 헤더·§5-6. 다중 스크립트/루트 컨테이너
(§5-7)는 답이 없어 미결 유지**[같은 날 후속 3에서 확정]** `Claim` 1회·전체 소유 + 루트의 `.Parent =`는 밖에서 허용(`base/claim-plan.md` §7-7, 아래 마지막 절).
## `H-159``_rerunRequired` 홀드 플래그 (사용자 제안, 확정) — `_installed` 흡수, `H-160` 정정
@ -408,3 +411,14 @@ observer 측에서 해당 emit 을 처리하는 함수를 만들어주는게 맞
짝. Slot 꼬리는 bind 자체가 확정 뒤라 `bindLifetime` 안의 `_catchUp`이 reconcile 1회를
맡는다 — 별도 끄기/호출 제거. 반영: `source-state-plan.md`(정의), `lifecycle-pattern.md`
셋 + 필드 목록, `slot-plan.md` 꼬리.
## [2026-08-28 후속 3] `Claim` 갈래 전량 확정 — `research/`에서 `base/claim-plan.md`로 승격
`H-148`/`H-161`이 남겨둔 research 문서 §5의 갈래 여덟(루트 키 / 물리 순서 /
claim된 부모 안 `New` / debug 검사 범위 / 표면 이름 / 마일스톤 / 여러 스크립트 /
`Parent` 재대입)이 같은 날 사용자와 대화형으로 전부 닫혔다. **결정과 사용자 원문은
`base/claim-plan.md` §7이 소스**, 대화 원문은 `session/2026-08-28-02-claim-promotion.md`.
이 파일 위쪽의 *"`base/claim-plan.md` §5 갈래 — 다음 배치"*류 서술은 당시 기록이다
(경로만 승격 뒤 이름으로 치환됨). 눈에 띄는 것 하나 — **`H-146`의 "루트는 밖에서
`.Parent =`"는 `H-148`에서 폐기됐다가 여기서 좁혀 복원됐다**(루트의 `Parent`는 만든
방법과 무관하게 부기 밖). `Claim`은 여전히 1회·전체 소유.

View file

@ -8,7 +8,7 @@
> 새로 만들고 `base/`에 반영한다 — **이 파일은 발견 당시의 기록**이라 각 항목의
> "갈래"는 선택 전 목록이니 반영 뒤엔 그대로 믿지 말 것.
>
> 상태: **[2026-08-28] 탐사 완료(발견 `H-150`~`H-157`, 🔴 0 · 🟡 5 · 🟢 3, §4 문항 7) → 같은 날 사용자와 대화형으로 전량 결정·반영 — 결정의 소스는 `-round10-followup.md`.** 후속 `H-158`~`H-162`도 같은 날 확정(`H-159`는 `/code-review` 권고 (a) `Refresh` 복원이 아니라 사용자 제안 **`_rerunRequired` 홀드**로). 미결은 `research/existing-mount-plan.md` §5의 갈래들뿐(개수는 거기가 소스). `H-147`~`H-149`는
> 상태: **[2026-08-28] 탐사 완료(발견 `H-150`~`H-157`, 🔴 0 · 🟡 5 · 🟢 3, §4 문항 7) → 같은 날 사용자와 대화형으로 전량 결정·반영 — 결정의 소스는 `-round10-followup.md`.** 후속 `H-158`~`H-162`도 같은 날 확정(`H-159`는 `/code-review` 권고 (a) `Refresh` 복원이 아니라 사용자 제안 **`_rerunRequired` 홀드**로). 남았던 `Claim` 갈래 여덟도 같은 날 전량 확정돼 `research/`에서 `base/claim-plan.md`로 승격(결정 기록은 그 §7). `H-147`~`H-149`는
> `H-143`~`H-146` 반영분에 `/code-review high`가 낸 10건 중 새 메커니즘·기존
> 결정 변경이라 문항으로 올린 셋(나머지 일곱은 반영 — `-round9-followup.md`
> 마지막 code-review 절).
@ -349,7 +349,7 @@ spurious 재발행은 `Source:Set`이 같은 값도 emit한다는 확정(`t18`
| **`H-154`** | `InstanceChildHandler` spurious dedup | (a) retractor `if nextValue == v then return end`(`SlotHandler` 동형) / (b) 그대로(정책 문서화) | **(a)** — `Slot`/`Ref`/Leaf가 이미 채택한 정책 |
| **`H-159`** | **[2026-08-28 `/code-review`, 반영 뒤]** `H-151`이 잃은 캐치업 — 바인드 **전**에 온 emit(특히 `Ref`)은 다시 안 온다 | (a) `_bindDestroying`/`resubscribeTail`에 **"묶이는 시점 1회 `Refresh`"**만 되살림(emit 경로의 `_epochs` 갱신은 `H-151`대로 `Update`만) / (b) `Ref` dep만 바인드 시 `.Revision` 대조 / (c) 계약으로 두고 사용자에게 "`Ref`를 dep으로 쓰는 Effect는 그 leaf 뒤에 두라" 문서화 | **(a)** — `H-151`의 근거("다음 emit이 잡는다")가 `Ref`엔 성립하지 않는다; (a)는 `H-151`을 되돌리는 게 아니라 "emit 경로만 미룬다"는 계약과 양립(바인드는 emit 경로가 아님) |
| **`H-160`** | leaf `Destroying` 콜백이 도는 cleanup 안의 `self:Rerun()`/`dep:Set()` — `canExecute`가 아직 참이라 죽는 inst에서 `fn`이 돌고 새 cleanup이 영구 고아 | (a) `rawRerun` 진입에서 `_cleanupRunning`이면 **버린다**(no-op) — "cleanup은 자기 생명주기를 못 바꾼다"의 `Rerun`판 / (b) `Destroying` 콜백이 `_consumeCleanup` **전에** `.Subscribed`류 표식으로 죽음을 먼저 세움(새 상태) / (c) UB 문서화 | **(a)** — 새 상태 없이 기존 플래그 하나로, `Unsubscribe` 경로와 같은 결과 |
| **`H-161`** | `H-148` 이후 **M5에 승인된 루트 부착 경로가 없다** + 여러 스크립트가 같은 `PlayerGui``Claim`하면 이중 claim error / 다중 quad UB라 `Claim`이 자기 동기 사례를 막는다 | (a) `Claim`**M5 스코프**로 당기고(프로바이더 마일스톤이라 자연스러움) `research/existing-mount-plan.md` §5-7·8 갈래를 같이 정한다 / (b) `Claim` 전까지 임시로 `H-146` 루트 예외(밖에서 `.Parent =`)를 M5 한정으로 되살림 / (c) 루트 컨테이너(부기 대상 아님)는 claim 없이 자식만 붙이는 얇은 표면 신설 | **(a)** — 임시 예외는 하루 만에 뒤집힌 것을 되살리는 것이고, (c)는 `Mount` 기각의 재개방. §5-7(다중 스크립트)은 `Claim`의 "전부 매핑" 계약이 **루트 컨테이너에는 안 맞는다**는 신호라 갈래를 그 문서에 적었다 |
| **`H-161`** | `H-148` 이후 **M5에 승인된 루트 부착 경로가 없다** + 여러 스크립트가 같은 `PlayerGui``Claim`하면 이중 claim error / 다중 quad UB라 `Claim`이 자기 동기 사례를 막는다 | (a) `Claim`**M5 스코프**로 당기고(프로바이더 마일스톤이라 자연스러움) `base/claim-plan.md` §5-7·8 갈래(승격 뒤 번호는 §7-7·§7-8)를 같이 정한다 / (b) `Claim` 전까지 임시로 `H-146` 루트 예외(밖에서 `.Parent =`)를 M5 한정으로 되살림 / (c) 루트 컨테이너(부기 대상 아님)는 claim 없이 자식만 붙이는 얇은 표면 신설 | **(a)** — 임시 예외는 하루 만에 뒤집힌 것을 되살리는 것이고, (c)는 `Mount` 기각의 재개방. §5-7(다중 스크립트)은 `Claim`의 "전부 매핑" 계약이 **루트 컨테이너에는 안 맞는다**는 신호라 갈래를 그 문서에 적었다 |
| **`H-163`** ✅ (a) → **(a)** | **[2026-08-28 `/code-review`, `H-159` 반영 뒤]** Slot 내부 Observer(`_listObserver`·`_baseObserver`)에도 홀드 발화가 걸려 재마운트의 `bindLifetime``materializeSlotTree` **도중** `reconcile`을 동기 실행 → 자리 이중 등록, 중첩 Slot이면 `canBound` error | (a) Slot이 자기 내부 Observer를 다시 묶기 전에 `_rerunRequired`**지운다**(재마운트 캐치업은 `activateList`가 이미 명시적으로 한다 — 이중) / (b) 홀드 발화를 사용자 Observer에만(내부 Observer는 브랜드로 구분 — 새 구분) / (c) 홀드 발화를 `bindLifetime` 안이 아니라 `materializeSlotTree` 끝(`blocker:OffWithoutEmit()` 뒤)으로 미룸 | **(a) → (a)** — (a)의 전제("재마운트 캐치업은 `activateList`가 이미 한다")는 감사 2라운드가 반증(그 분기는 앵커만 옮긴다) → 트리 확정 뒤 끄고 묶고 홀드가 있었으면 reconcile 1회. 소스는 `-round10-followup.md` |
| **`H-164`** ✅ (c) — 문항 전제 정정 | Observer 홀드 발화가 `emitFrom = nil`로 오면 계약("`nil` = 설치 발화")과 구분 불가 — `if emitFrom == nil then initOnly()`로 짠 소비자가 변경을 놓침 | (a) 홀드 시 **마지막 `from`을 보관**(`_rerunRequired = from`, 진리값으로 플래그 겸용)해 그것을 넘김 / (b) 전용 센티널(`HeldEmit`) / (c) 계약 문구만 "`nil` = 설치 **또는** 묶일 때 캐치업" | **(c) — 문항 전제 정정**: 홀드 발화는 출처 있는 통지가 아니라 "묶였으니 값을 읽어라"라 설치 발화와 같은 종류 — `nil` = 출처 없음(설치 또는 캐치업). (a)의 `from` 보관은 사용자 기각(여러 홀드가 오면 앞 것이 날아감, 보관할 이유 없음). 소스는 `-round10-followup.md` |
@ -390,10 +390,10 @@ emit이 오는 dep**에만 성립한다. 갈래·권고는 §4 표.
`H-148``H-146`의 루트 예외를 폐기하고 `Claim`은 "M5 이후" 백로그라, M5(프로바이더·
`D`·`InstanceChildHandler`)가 끝나도 quad가 만든 트리를 `PlayerGui`에 붙이는 승인된
경로가 코퍼스에 없다. 그리고 `research/existing-mount-plan.md`의 *이중 claim은 error /
경로가 코퍼스에 없다. 그리고 `base/claim-plan.md`의 *이중 claim은 error /
여러 quad가 한 트리를 claim은 UB / 부기 대상 자식은 전부 매핑* 계약을 그대로 두면
`Shop.client.luau``Inventory.client.luau`가 각각 `Claim(PlayerGui, …)`하는 **가장
흔한 사례가 error**다 — 그 문서 §5엔 이 문항이 없었다(추가: §5-7·§5-8). 갈래·권고는
흔한 사례가 error**다 — 그 문서 §5엔 이 문항이 없었다(추가: §5-7·§5-8 — 승격 뒤 `base/claim-plan.md` §7-7·§7-8). 갈래·권고는
§4 표.
## §5 이상 없다고 확인한 것

View file

@ -31,7 +31,7 @@
| — | `H-139` `New`/`D` 파이프라인 | ✅ **의사코드 신설** — 쓰면서 `H-142` 발견 |
| — | `H-132`/`H-137`/`H-140` | ✅ Q1~Q3 처리 때 닫힘 |
| — | `H-142` 해시 파트 `Parent` 순서 | ✅ **확정·반영** — props에 `Parent` 금지(순서 문제 소멸) |
| — | `H-143`~`H-146` (`/code-review high` 발견 중 새 메커니즘 넷) | ✅ 2026-08-27 확정·반영 → **⚠️ [2026-08-28] 10라운드가 셋을 뒤집음**(`-round10-followup.md`가 소스): `H-143` 소멸(`fn` 안 자기 해제 지원 폐기, `H-147`) / `H-144` 꼬리는 유지하되 `Refresh` 먼저는 폐기(`H-151`), 진입점은 `EffectHandle` 자기 것 (b) / `H-145` weak-key **유지** / `H-146` 루트 예외·전용 문구 폐기(`H-148` → `Claim`) |
| — | `H-143`~`H-146` (`/code-review high` 발견 중 새 메커니즘 넷) | ✅ 2026-08-27 확정·반영 → **⚠️ [2026-08-28] 10라운드가 셋을 뒤집음**(`-round10-followup.md`가 소스): `H-143` 소멸(`fn` 안 자기 해제 지원 폐기, `H-147`) / `H-144` 꼬리는 유지하되 `Refresh` 먼저는 폐기(`H-151`), 진입점은 `EffectHandle` 자기 것 (b) / `H-145` weak-key **유지** / `H-146` 루트 예외·전용 문구 폐기(`H-148` → `Claim`; **루트 예외는 같은 날 후속 3에서 좁혀 복원**`base/claim-plan.md` §5) |
**[2026-08-27] Q1~Q3는 `base/`·`ROADMAP.md`에 반영했다** — 반영 중 드러난 것과
열어둔 확인은 아래 "반영 기록" 절. **같은 날 이어서 Q4~Q8·Q10·`H-138`·`H-139`를
@ -742,11 +742,11 @@ epoch 를 전부 잘 설정해주는게 나을수도 있어. 왜냐하면 처음
5번째 인자 근거 정리(weak가 되며 "옛 키 잔존" 근거는 사라지고 "지속 클로저
없음"만 남음), `ROADMAP.md` M3 `getBookkeeping` 체크박스.
### `H-146`⛔ [2026-08-28 폐기, 10라운드 `H-148`] 루트는 밖에서 `.Parent =`가 아니라 quad가 `Claim`으로 소유 — 아래는 하루 살았던 결정
### `H-146`⚠️ [2026-08-28 10라운드 `H-148`에서 폐기됐다가 같은 날 후속 3에서 좁혀 복원] 루트의 `.Parent =`는 밖에서 허용, 이미 있는 트리 소유는 별개 표면 `Claim`(`base/claim-plan.md` §5) — 아래 원 서술은 다시 유효(전용 문구만 철회)
**`research/existing-mount-plan.md`와 `-round10-followup.md` `H-148`이 소스.** 전용 문구도 철회.
**`base/claim-plan.md`와 `-round10-followup.md` `H-148`이 소스.** 전용 문구도 철회.
#### (폐기) 루트는 금지 범위 밖, `Mount` 표면 없음, 거부는 전용 문구 (a)
#### (루트 예외·`Mount` 없음은 복원돼 유효, 전용 문구만 폐기) 루트는 금지 범위 밖, `Mount` 표면 없음, 거부는 전용 문구 (a)
- 인용문 *"외부에서 직접 Parent 설정해주지 말것"*의 범위를 **quad가 관리하는
자식 자리**로 명문화. 루트 부착은 엔진마다 다른 최종 사용자 코드. (b)

View file

@ -60,7 +60,7 @@ high` 7패스의 수정)를 처음부터 다시 트레이싱한 결과. 발견
| `H-143` | 🟡 | **[2026-08-27 `/code-review high`]** `fn` 안에서 `self:Unsubscribe()`를 부르면(문서가 허용하는 자리) `Rerun``fn`의 반환 cleanup을 그대로 `_cleanup`에 저장하고 `_installed = true`로 되돌려 **아무도 소진 못 하는 cleanup**이 남는다 — "마지막 cleanup 정확히 1회" 계약 위반 | `effect-plan.md` `Rerun` | 사냥 밖 — 리뷰 발견, 처방은 새 메커니즘. **[2026-08-27] 확정 (a) → [2026-08-28] 소멸**(10라운드 `H-147`: `fn` 안 자기 해제 자체가 폐기) | — |
| `H-144` | 🟡 | **[2026-08-27 `/code-review high`]** `EffectHandle.Subscribe = Observer.Subscribe` 배정이라 `Unsubscribe`로 cleanup을 소진한 핸들을 다시 `Subscribe`하면 `.Subscribed = true`만 서고 **재설치(`Rerun`)가 없다** — leaf 재바인드 경로(`_bindDestroying`의 `not _installed → Rerun`, `H-65`)와 비대칭 | `effect-plan.md` | 리뷰 발견, 처방은 새 메커니즘. **[2026-08-27] 확정 (a) 두 구독 진입점 모두 꼬리; 진입점은 (b) `EffectHandle` 자기 것 — [2026-08-28] `Refresh` 먼저는 폐기(10라운드 `H-151`)** | — |
| `H-145` | 🟡 | **[2026-08-27 `/code-review high`]** 최상위 `SlotHandler` retractor가 `setLength(inst, k, 0)`으로 해제할 때 `bk(inst).indexOfElement[slot]`**안 지워진다**(해제 호출엔 요소가 없고 `setLength``element ~= nil`일 때만 쓴다) — 교체마다 옛 Slot이 부모 `bk`에 강참조로 쌓여 부모가 죽을 때까지 산다(`InstanceChildHandler` 쪽은 5번째 인자를 안 넘기는 것으로 닫았지만 Slot은 `Length`가 State라 요소가 필요하다) | `dispatch-core-plan.md` `setLength` × `slot-plan.md` 494 | 리뷰 발견, 처방은 새 메커니즘. **[2026-08-27] 확정 (a) weak-key** | — |
| `H-146` | 🟡 | **[2026-08-27 `/code-review high`]** `H-142`(props에 `Parent` 금지, 대입은 자식을 받는 쪽만)를 닫고 나니 **루트**(`ScreenGui` → `PlayerGui`)를 붙이는 승인된 경로가 코퍼스 어디에도 없다 — 유일한 선례는 v1 `Mount(ScreenGui, …)`, `slot-plan.md` 233행의 *"v2는 mount 함수 자체가"*의 그 함수는 어느 표면에도 없다. 부수로 거부 배선의 에러가 일반 매치 실패 메시지(*"provider가 초기화됐는지 확인"*)라 오해를 부른다 | `bind-system-plan.md` `H-142` | 리뷰 발견, 처방은 새 표면. **[2026-08-27] 확정 (a) 루트 예외 + 전용 문구 → [2026-08-28] 둘 다 폐기**(10라운드 `H-148`: 루트는 `Claim`으로 quad 소유, `research/existing-mount-plan.md`) | — |
| `H-146` | 🟡 | **[2026-08-27 `/code-review high`]** `H-142`(props에 `Parent` 금지, 대입은 자식을 받는 쪽만)를 닫고 나니 **루트**(`ScreenGui` → `PlayerGui`)를 붙이는 승인된 경로가 코퍼스 어디에도 없다 — 유일한 선례는 v1 `Mount(ScreenGui, …)`, `slot-plan.md` 233행의 *"v2는 mount 함수 자체가"*의 그 함수는 어느 표면에도 없다. 부수로 거부 배선의 에러가 일반 매치 실패 메시지(*"provider가 초기화됐는지 확인"*)라 오해를 부른다 | `bind-system-plan.md` `H-142` | 리뷰 발견, 처방은 새 표면. **[2026-08-27] 확정 (a) 루트 예외 + 전용 문구 → [2026-08-28] 둘 다 폐기****[2026-08-28 후속 3] 루트 예외는 같은 날 좁혀 복원**(루트의 `.Parent =`는 밖에서 허용, `base/claim-plan.md` §5; 전용 문구 철회만 유지; 이미 있는 트리 소유는 별개 표면 `Claim`, `base/claim-plan.md`) | — |
---

View file

@ -12,18 +12,15 @@
---
## ⭐ 최우선 — `Claim` 갈래 (2026-08-28 갱신)
## ⭐ 최우선 — 비어 있음 (2026-08-28 갱신)
**[2026-08-28] 10라운드 §4 문항 7건과 그 반영분의 후속(`H-158`~`H-164` — `EmitReceive`·
`Observer:_catchUp` 포함)까지 사용자와 대화형으로 전량 결정·반영됐습니다**(소스는
`qa-request/pre-implementation-handtrace-round10-followup.md`). 남은 건 하나 —
**M2 게이트 아님**:
- **`research/existing-mount-plan.md` §5** — 루트/템플릿을 quad가 소유하는
`Claim` + `D.Mapper`의 갈래들(루트 디스크립터 이름·물리 순서 계약·비루트
사용·debug 검사 범위·표면 이름 … — 개수는 그 문서 §5가 소스). 방향은 확정, M5
스코프(`H-161`). **특히 §5-7**(여러 스크립트/여러 quad가 같은 `PlayerGui`
쓰는 경우 — "전부 매핑" 계약이 루트 컨테이너엔 안 맞는다는 신호, 권고 없음)이
`Claim` 설계의 핵심 미결입니다.
`qa-request/pre-implementation-handtrace-round10-followup.md`). 같은 날 마지막으로
남아 있던 **`Claim` 갈래 여덟도 전량 확정**돼 `research/`에서 `base/claim-plan.md`
승격됐습니다(결정 기록은 그 §7, 원문은 `session/2026-08-28-02-claim-promotion.md`,
해소 요지는 `archive/question-resolved.md`의 "`Claim` 갈래" 절). **M2 착수를 막는
항목은 없습니다.**
**[2026-08-27] 9라운드**(Q1~Q10·`H-138`·`H-139`·`H-142`·`H-143`~`H-146`)는
전량 처리·반영됐습니다 — 소스는 `-round9-followup.md`.
@ -77,6 +74,21 @@
> `base/dispatch-core-plan.md`(0-A/0-Z가 반영된 디스패치 코어 — 열네 번째
> 세션에 `bind-system-plan.md`에서 분리 신설).
## ⭐ M5 착수 전 — `Claim` 문항 넷 (2026-08-28 신설, M2 게이트 아님)
**[2026-08-28 저녁, `/code-review high`] `Claim` 승격 뒤 M5 착수 전 문항 넷** — M2
게이트 아님, 결정은 M5 착수 때까지면 됨. 소스는 `base/claim-plan.md` §10(권고 포함):
- **A.** claim한 inst의 gcconn/gchold (0) 셋업 자리 — 프로바이더 op(`nativeAdopt` 가칭) /
`Claim` 본체를 프로바이더로 / (0)을 quad-base로. 권고 op.
- **B.** 같은 `inst` 이중 claim 판정 레지스트리 — `elementOwner`는 못 씀(claim한 inst를
Slot 요소로 쓰는 §6과 충돌). 권고 per-inst `InstData` 플래그.
- **C.** 엔진이 자식을 넣는 컨테이너(`PlayerGui`, `ResetOnSpawn`)와 own-all 계약 —
권고 "UB로 명문화, 흔한 경로는 `.Parent =`". 매퍼 생성기 범위에 컨테이너 포함 여부도.
- **D.** `type <Class>Param`의 children 배열 파트 — 원소 타입 파라미터 vs 필드만 공유.
권고 파라미터 + 스파이크.
- 부수(우선순위 낮음): `Claim` debug 검사 범위는 `research/debug-tooling-plan.md`
"열린 질문" 절로 이관돼 있음 — 디버깅 도구 설계 때.
## 1. 용어 정리 — 아직 안 정해진 것만 (사용자 요청, 진행 중)
사용자 원 메모: "quad는 register라던가 좀 부정확하거나 느낌이 바로 와닿지

View file

@ -511,6 +511,17 @@ Tween mock 등 동적 동작 포함")와 목적이 다름:
- Attribute 이름 네임스페이싱(`__quadSource`류)과 노출 정보 범위(스크립트
전체 경로를 노출해도 되는지, 파일명만 남길지 등 보안/정보노출 고려).
**`Claim` debug 검사의 범위 — [2026-08-28 `base/claim-plan.md` §7-4에서 이관]**
- `Claim(inst, D.Mapper…)`은 이름 중복·부재를 UB로 두고 debug 모드에서만 `seen`
맵으로 잡는다(사용자 제안). **어디까지 잡을지는 여기서 정한다** — 후보: 이름
중복 / 부재 / 클래스 불일치 / 미매핑 부기 대상 자식 / 디스크립터 순서와 물리
순서 불일치(web) / 같은 `inst` 이중 claim / 루트 센티널 `D.Mapper.Root`가 자식
자리에 온 것. 사용자 판단(2026-08-28): *"디버깅 도구 만들 때 고려해야할 점으로
옮겨져야해. 부분 부분 디버깅 가능성을 아직 다 논한게 없어서 지금 그림으로 보면
작은 그림을 먼저 그리는거라서, 미결상황으로, 위치 이동이 필요함"* — 즉 "debug
모드가 무엇을 언제 검사하는가"의 큰 그림(이 문서) 안에서 같이 정할 것.
**백로그(채택 여부만 남음, 핵심 설계와 무관)**
- 외부 변경 감지(핵심 설계 방향 4번)를 보조 신호로라도 실제로 켤지 — 타이밍

View file

@ -176,6 +176,16 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
무시될 수도 있다. 지금 설계로 막을 일이 아니라 **문서화할 사실**이다
(버전 정책의 타입 레벨 축은 `type-version-check`가 이미 다룬다 —
이건 그 런타임 판별 판). 코퍼스 어디에도 언급이 없어서 여기 등록한다
21. **[2026-08-28 신설] 이미 있는 트리 — `Claim`과 루트 `.Parent`** —
`base/claim-plan.md` §9의 문서화 대상: (1) claim-once·own-all 계약 — 부기 대상
자식은 전부 매핑, 디스크립터 순서가 정본, 숏핸드 `UI*`는 (i) 템플릿에 없고
quad가 만들거나 (ii) 실제 객체로 매핑하거나 둘 중 하나(섞으면 UB), 이름
중복·부재는 UB(debug에서 error); (2) **루트의 `.Parent =`는 밖에서, 그 아래는
절대 직접 하지 말 것** — props에 `Parent`를 넣으면 일반 매치 실패 문구가 나오는
이유도 여기서 설명; (3) 여러 스크립트가 한 `PlayerGui`를 쓸 땐 각자 `ScreenGui`
만들어 `.Parent = PlayerGui`, PlayerGui 직하 Slot을 공유해야 하면 `Claim`을 한
번 하고 Slot을 반환하는 중간 모듈 패턴(**단 PlayerGui 자체를 claim하는 것은
`base/claim-plan.md` §10-C 미결** — 확정 전엔 쓰지 말 것)
(13번이었던 "Fusion/Vide 경험자용 비교 섹션"은 2026-08-06 재분류로 아래 6번
`quadnomicon`으로 이동)

View file

@ -1,162 +0,0 @@
# 이미 있는 트리를 quad가 소유하기 — `Claim` + `D.Mapper` (가칭)
> **[2026-08-28 신설, 사용자 발의]** 10라운드 `H-148`(`Parent` 거부 문구)을
> 논의하다 **더 큰 표면의 공백**이 드러나 만든 문서. 상태: **설계 논의 중 —
> 방향은 사용자 확정, 갈래 몇 개 미결(§5)**. M2 착수 게이트 아님, **M5 스코프**
> (**[2026-08-28 `H-161`]** "M5 이후"에서 당김 — 루트 예외 폐기 뒤 M5의 유일한 루트
> 부착 경로; 프로바이더 op가 필요하니 M5가 자연스러운 자리). 결정이 나면 `base/`
> 승격한다.
>
> **`archive/existing-instance-bind-rejected.md`(2026-08-14 기각)와의 관계**:
> 그 기각은 *"이미 있는 Instance에 나중에 새 props를 다시 바인드"*였고 사유는
> "quad가 만들지 않은 트리의 자식 구성을 바깥이 밀고 당기면 `setLength`/
> `setOffsetSource` 부기가 깨진다"였다. 이 문서는 그 반대 방향 — **한 번
> claim하면 quad가 소유하고 직계 자식은 사용자가 전부 매핑한다**는 계약이라
> claim 뒤엔 quad가 만든 트리와 같은 불변식이 성립한다. **재바인드는 여전히
> 미지원**(claim은 1회, 디스크립터는 `Processed`로 소진). 그 archive엔 "좁은
> 형태로 부활"이라는 배너를 달 것(반영 시).
## 1. 왜 필요한가 (사용자 원문)
`H-146`이 "루트는 사용자가 밖에서 `.Parent =`"로 닫혔는데, 사용자가 이어서
지적했다: *"slot 은 물리 장치에 mount 할 방법이 거의 존재하지 않음. Parent =
처럼 마운트 할 방법이 없는데? 그럼 PlayerGui 가 상위에 있고 거기에 GUI 를
여럿 바운딩 해야해서 `Slot { Shop{} … }` 하는게 안 될것 같은 느낌이 듦. 이건
Parent 이상의 문제인것 같아."* — 즉 **루트가 Slot일 수 없다**(Slot은 quad가
부기를 가진 부모 `inst` 아래에만 산다). 그리고: *"생성할 요소들 자체가 너무
많은 경우 Clone 이 엇청 더 싸서, 그 Clone 된 것 아래 quad 를 바인딩 할 방법이
있으면 좋은것도 사실인듯. … web 에서도 템플릿에 의해 유효한 요소일꺼고,
roblox 에서도 보면서 만들어낸 GUI를 바인딩하는건 흔한 요구라서 이 역시 흔한
필요일꺼야."*
결론(사용자): *"이런 방식으로, 이미 있는 PlayerGui 아래 마운트를 거는거지.
… 이것도 똑같이 Quad 가 소유하게 될 요소가 되는거지. … 이렇게 끝내면 parent
를 설정할 문제 자체가 사라져"* — **`H-146`의 루트 예외와 `H-148`의 문구 문제가
같이 소멸**한다.
## 2. 모양 (사용자 스케치 + 확정된 것)
```lua
local M = D.Mapper -- 정의는 D 안에 산다. 유저가 필요하면 꺼낸다
local cloned = Claim(template:Clone(), M.Frame "root" { -- ← 루트 이름은 미결(§5-1)
M.TextLabel "Title" { Text = title },
M.Frame "List" {
Slot { … }, -- 기존 부모 아래 Slot — 이제 가능
},
BackgroundColor3 = color, -- props는 New와 같은 derive 테이블
}) -- -> cloned (claim한 루트 Instance)
```
- **`D.Mapper.<Class> "Name" { … }`는 Instance를 만들지 않고 디스크립터만
만든다**(브랜드 `Mapper`류) — derive 테이블 + 매칭 키. `D.Frame`에 직접 얹지
않는다(사용자: *"D.Frame 에 바로 바인딩은 위험한듯. 의미가 겹쳐버려"*).
- **`Claim(inst, descriptor) -> inst`가 최상위**. `inst`는 quad 밖에서 온 것
(PlayerGui, `Clone()` 결과, Studio에서 만든 GUI). 이 호출로 `inst`와 매핑된
하위 전부가 **quad 소유**가 된다 — `New`가 만든 것과 같은 gcconn/gchold·부기.
- **처리 순서는 `New`와 반대 방향에서 시작한다.** `New`는 안쪽 생성자가 먼저
평가돼 자연히 bottom-up이지만, 매퍼의 안쪽은 평가 시점에 자기 Instance를
모른다(부모가 아직 없다). 그래서 `Claim`이 **DFS로 내려가며 이름으로 해석 →
자식부터 `drive` → 올라오며 부모 `drive`**. 사용자: *"핸들러가 된다면 위험해.
일반 생성과 다르게, 상위 부터 처리하거든 … DFS 로써, 내려가는게 먼저고 그
뒤에서 derive 를 걸어야해. 이건 derive 에선 구현하지 않고, 그 위의 무언가로써
구현되어야할듯."* — `drive`/핸들러 층은 안 바뀌고 그 **위의 한 겹**이다.
- **매핑된 정적 자식은 `InstanceChildHandler`와 같은 부기(`setLength(inst,k,1)`)만
하고 `.Parent =`는 안 한다**(이미 거기 있다). Slot은 평소처럼 `native*`
기존 부모 아래 끼운다.
- **디스크립터는 1회용** — claim 뒤 `Processed`로 소진(`PreRef`와 같은 관용구).
재사용·이중 claim은 error.
- **매칭은 프로바이더 주입 op** `nativeFindChild(inst, key)`(가칭) — Roblox는
`Name`, web은 id/selector. **quad-base가 순회·부기 전반을 구현하고
프로바이더는 이 핸들만 낸다**(사용자: *"quad-base 에서 전반을 구현해주고
필요 핸들을 구현하라고 남기는건 괜찮은 생각"*).
- **여러 quad 인스턴스가 한 트리를 claim — UB**(사용자 확정).
- **`H-142`(props에 `Parent` 금지)는 그대로.** 루트가 quad 소유가 되므로
`H-146`의 "루트는 밖에서 `.Parent =`" 예외는 **폐기**.
## 3. 계약 — 자식은 전부 매핑한다
사용자: *"모든 개체를 유저가 직접 네임을 매핑해서 derive 테이블 안에서 내부
요소를 전부 매핑해준다를 계약으로 잡으면 문제가 없다고 생각해."*
- **부기 대상(그려지는 자식)은 전부 매핑해야 한다.** 안 된 자식이 남으면
`nativeInsert`의 삽입 위치(web은 곧 DOM 순서)와 Length/Offset이 어긋난다.
- **숏핸드(`UICorner` 등 `UI*`)는 부기 대상이 아니다** — 그려지지 않고 Roblox에만
있으며 단순 `Parent` 대입 요소. 사용자 확정: *"숏핸드를 quad 에서만 직접
쓰거나 … 아니면 실제 UI 객체를 바인딩해서 숏핸드를 안 쓰거나"* — 둘 중 하나:
(i) 템플릿엔 `UI*`가 없고 quad가 숏핸드 키로 만든다, (ii) 템플릿의 `UI*`
`M.UICorner "UICorner" {…}`처럼 **실제 객체로 매핑**하고 그 부모에 숏핸드
키는 안 쓴다. 섞으면(템플릿에 `UICorner`가 있는데 숏핸드 키도 씀) 둘이
생기는 것은 UB.
- **이름 중복·부재는 UB**(사용자 확정). **debug 모드**에선 `seen` 맵으로 중복을
잡아 error(사용자 제안) — 부재·클래스 불일치도 같은 자리에서 검사.
## 4. 이 문서가 여는 것
- **루트**: `Claim(PlayerGui, M.ScreenGui "…" { Slot {…} })` — PlayerGui가
quad 소유 부모가 되어 Slot이 그 아래 산다. `ScreenGui``New`로 만들어
붙이는 경우도 `Claim(PlayerGui, M.PlayerGui(...) { New "ScreenGui" {…} })`처럼
정적 자식으로 들어간다(→ §5-3 루트 이름 문제).
- **템플릿 대량 생성**: `template:Clone()``Claim` — 각 사본이 독립 소유.
Claim이 Instance를 돌려주므로 **Slot 요소로도 그대로 쓸 수 있다**(요소는
`inst`) — "요소가 너무 많은 경우"의 답.
- **비루트 사용**: `New "Frame" { Claim(clone, …) }` — 반환된 `inst`가 정적
자식으로 들어가면 `InstanceChildHandler``Parent =`와 부기를 한다. 평가
순서상 `Claim`이 먼저 끝나므로 bottom-up이 유지된다.
## 5. 미결 — 다음 배치 문항
1. **루트 디스크립터의 이름** — 루트는 `Claim``inst`를 직접 받으니 매칭 키가
필요 없다. 사용자: *"최상위는 이름을 뭐로 둬야할지 아직 모르겠음. 비워두는걸
D.Mapper.Frame{} 으로 제공하는건 더 나빠보이는데. 아니면 테이블로써
MapperRoot = {} Mapper.Frame (MapperRoot) {} 모양이 되어도 될것같음."*
갈래: (a) 센티널 `MapperRoot`(`M.Frame(MapperRoot) {…}`) / (b) 루트는
`Claim(inst, { … })`처럼 클래스 없는 맨 테이블(클래스는 `inst`가 이미 안다)
/ (c) 이름을 받되 무시. **권고 (b)** — 루트 클래스를 두 번 말하지 않고,
`M.<Class>`는 "찾아야 하는 자식"에만 쓰여 뜻이 하나가 된다. 단 타입(`D`
생성기가 만든 props 타입)을 잃으므로 `Claim<<"Frame">>(inst, {…})`처럼
타입 인자로 보완 — `New<<X>>`와 같은 관용구.
2. **물리 순서 계약** — 디스크립터 배열 순서와 기존 트리의 실제 순서가 다를 때.
Roblox는 물리 순서가 의미 없어 무관, web은 DOM 순서라 `nativeInsert` 위치가
어긋난다. 갈래: (a) 디스크립터 순서가 정본이고 일치는 사용자 책임(UB,
debug 검사) / (b) claim 시 quad가 `nativeMove`로 실제 순서를 디스크립터에
맞춘다. **권고 (a)** — "이미 있는 걸 그대로"의 취지, Roblox에선 비용 0.
3. **`New`로 만든 자식을 claim된 부모에 넣는 것** — 위 §4 첫 항목. 정적 자식이니
`InstanceChildHandler``Parent =`를 하면 되고 새 결정은 없어 보이나,
"매핑(이미 있음)"과 "생성(새로 붙임)"이 한 배열에 섞이는 것이 계약상
괜찮은지 확인 문항.
4. **debug 검사의 범위** — 이름 중복 / 부재 / 클래스 불일치 / 미매핑 부기
대상 자식 / 같은 quad의 이중 claim 중 어디까지. 권고: 전부(debug에선 싸다).
5. **표면 이름**`Claim`/`Mount`/`Adopt`, `D.Mapper`/`D.Existing`.
`Mount(root, parent)``H-146`에서 기각한 사유("부기 없는 대상에 quad 객체
주입")는 여기 반대로 적용된다 — claim은 부기를 *세우는* 행위. 권고 `Claim`.
6. **마일스톤** — M5 이후(`nativeFindChild`가 프로바이더 표면). `ROADMAP.md`
백로그에 포인터만. **[2026-08-28 `/code-review`, `H-161`]** 단 `H-146` 루트
예외를 폐기한 지금 **M5에 승인된 루트 부착 경로가 없다** — (a) `Claim`을 M5
스코프로 당김 / (b) `Claim` 전까지 M5 한정 임시 예외 / (c) 루트 컨테이너용
얇은 표면. **사용자 확정 (a)**(*"M5 스코프로 올라가도 될것으로 보임"*) — 헤더와
`ROADMAP.md` M5 체크박스에 반영. §5-7(다중 스크립트)은 여전히 미결.
7. **[2026-08-28 `/code-review`, `H-161`] 여러 스크립트/여러 quad가 같은 루트
컨테이너를 쓰는 경우** — 위 "이중 claim error / 다중 quad UB / 부기 대상 자식
전부 매핑"을 그대로 두면 `Shop.client.luau``Inventory.client.luau`가 각각
`Claim(PlayerGui, …)`하는 **가장 흔한 사례가 막힌다**. 이건 "전부 매핑" 계약이
**루트 컨테이너**(부기 대상이 아닌 `PlayerGui`·`CoreGui`류 — 자식 순서가
의미 없고 quad가 그 형제들을 관리하지 않는다)에는 안 맞는다는 신호다. 갈래:
(a) `Claim`은 **부기를 갖는 노드**에만, 루트 컨테이너엔 "quad가 만든 자식
하나를 붙이는" 별개 표면(부기 없음, 여러 스크립트 공존, 이름은 §5-5와 같이) /
(b) `Claim`에 "이 노드의 다른 자식은 관리하지 않는다"(부분 매핑) 모드 — 단
web처럼 물리 순서가 의미 있는 엔진에선 위험 / (c) 다중 claim을 허용하되 각
claim이 자기가 매핑한 자식만 소유(UB 대신 정의) — 부기 충돌 없음이 조건.
**권고 없음** — (a)는 `H-146`에서 기각한 `Mount(root, parent)`의 재개방과
경계가 얇고, (b)(c)는 계약을 약화시킨다. 사용자 판단.
8. **매핑된 정적 자식의 `Parent` 대입** — §2는 "부기만, `.Parent =`는 안 한다"인데
`InstanceChildHandler``v.Parent = inst`가 계약(`dispatch-core-plan.md`
`H-134`). 같은 핸들러를 쓰면 이미 거기 있는 자식에 같은 값을 재대입(엔진
no-op)하는 것뿐이라 별도 핸들러가 필요 없어 보인다 — 확인 문항(권고: 같은
핸들러, 재대입 감수).
## 6. 반영 시 고칠 자리 (승격 때 체크리스트)
`archive/existing-instance-bind-rejected.md` 배너 / `base/bind-system-plan.md`
`H-142` 항목의 `H-146` 루트 bullet(폐기 → 이 문서 포인터) / `base/slot-plan.md`
"동적 자식은 반드시" 절 각주 / `ROADMAP.md` M5 `Property.luau` 전용 문구 삭제 +
백로그 항목 / `research/documentation-content-map.md` / `question.md`.

View file

@ -1983,7 +1983,7 @@ Q4(`EffectHandle` 네 진입점 의사코드 — Observer 것 재사용, `Unsubs
결정·반영. **뒤집힌 것 둘**: `fn`/cleanup은 자기 구독을 못 바꾼다(`H-147` (A) —
어제 `H-143`의 원샷 지원 소멸, `rawRerun(force)`/`Rerun` 분리, 네 진입점 `_running`
가드) / 루트는 밖에서 `.Parent =`가 아니라 **quad가 `Claim`으로 소유**(`H-148` →
`research/existing-mount-plan.md`, 2026-08-14 기각과 다른 claim-once·own-all).
`base/claim-plan.md`, 2026-08-14 기각과 다른 claim-once·own-all).
나머지: Observer 진입점 인라인(`H-149`) / `Effect._blocker` 제거(`H-150`) /
`_epochs`는 emit 때만 갱신, `Refresh` 캐치업 폐기 + "게이트는 emit 경로만 미룬다"
계약(`H-151`) / `GateNode` 브랜드 등록(`H-152`) / Store 예약 이름 런타임
@ -1995,3 +1995,19 @@ Q4(`EffectHandle` 네 진입점 의사코드 — Observer 것 재사용, `Unsubs
(전파 루프는 `sub:_receive(from)`만, 사용자 지시) · Slot 재마운트 캐치업 (a) ·
`emitFrom == nil` = 출처 없음 · `Observer:_catchUp()`. 소스는
`qa-request/pre-implementation-handtrace-round10-followup.md`.
- **`session/2026-08-28-02-claim-promotion.md`** — 10라운드가 남긴 `Claim` 갈래 여덟을
사용자가 한 메시지로 답하고(1~5) 에이전트가 §5-7을 "한 quad·여러 스크립트" 문제로
다시 세워 되물은 뒤 전량 확정 → `research/existing-mount-plan`**`base/claim-plan.md`
승격**. 루트 키 센티널(맨 테이블 + `Claim<<T>>`는 타입 자동완성 손실로 기각, `type
<Class>Param` 공유) / 디스크립터 순서 정본 / `New` 자식 허용(위치는 프로바이더 몫) /
debug 검사 범위는 `debug-tooling-plan.md`로 / 이름 `Claim`·`D.Mapper` / **루트의
`.Parent =`는 밖에서 허용으로 복원**(`H-146` 예외가 `Claim`한 루트까지 넓혀 복원,
`Claim`은 1회·전체 소유, PlayerGui 직하 Slot 공유는 중간 모듈) / 매핑된 자식은
`InstanceChildHandler` 그대로. `nativeFindChild` 주입 op 등록. `question.md` 최우선 절 비움.
감사 6라운드(발견 2→3→1→3→2→0, 3~5라운드는 전부 낮의 "폐기" 배너가 저녁의 복원을
모르던 것) → `/code-review high` 10건: 서술·라벨·stale 여섯 반영, **새 메커니즘 넷**은
`base/claim-plan.md` §10 + `question.md` 문항으로(gcconn/gchold 셋업 자리 / 이중 claim
레지스트리 / `PlayerGui` own-all vs `ResetOnSpawn` / `<Class>Param` 배열 파트). 리뷰가
잡은 실질 정정: `Claim<T>` 추론은 `Clone()``Instance`를 돌려줘 성립 안 함 →
반환은 inst 타입 그대로 / `Processed` 관용구는 플래그 절반만 / claim된 inst에선
`PreRef`/`OnCreated` 불변식이 약해짐.

View file

@ -16,7 +16,7 @@
없다"*고 한 것을 스스로 *"엄청난 모순이네"*로 뒤집은 자리.
2. `H-148`에서 사용자가 더 큰 공백을 짚음 — 루트가 Slot일 수 없다(`PlayerGui`
아래 `Slot { Shop{} }` 불가). 2026-08-14 기각(재바인드)과 다른 방향(claim-once·
own-all)임을 archive와 대조해 확인하고 `research/existing-mount-plan.md` 신설.
own-all)임을 archive와 대조해 확인하고 research 문서 신설(당시 `research/existing-mount-plan`, 같은 날 세션 02에서 `base/claim-plan.md`로 승격).
`H-146`의 "루트는 밖에서 `.Parent =`" 예외는 하루 만에 폐기.
3. `H-149`~`H-154`는 권고대로. `H-151`에서 사용자가 *"우린 애초에 Refresh 를 할
필요가 없는거야"* — 어제 `H-144`에서 세운 "`Refresh` 먼저" 하위 결정이 소멸.

View file

@ -0,0 +1,135 @@
# 2026-08-28 (02) — `Claim` 갈래 확정, `research/`에서 `base/claim-plan.md`로 승격
> 이 파일은 **원문 기록**이다(시행착오 포함). 지금 유효한 설계는 `base/claim-plan.md`.
> 앞 세션 `session/2026-08-28-01-handtrace-round10-resolution.md`가 만든 research
> 문서(옛 `existing-mount-plan`) §5 갈래 여덟을 사용자와 대화형으로 닫은 기록.
## 경위
10라운드 후속 2까지 커밋(`f020f3f`)한 뒤 사용자가 *"`research/existing-mount-plan.md`
관해서 다뤄보자. 우선 5. 미결을 보자"*로 이어갔다. 사용자가 항목 1~5를 먼저 적어
보냈고("내가 적어둔걸 볼래?"), 에이전트가 사실 셋을 확인한 뒤 의견과 함께 답이
없던 §5-7·§5-8을 되물었다. 사용자가 둘을 답해 전량 확정.
## 사용자 원문 (1차 메시지)
> 1. 의 권고 b는 문제가 생겨. 루트에 대해서 {} 안의 타입체크와 타입 자동완성이
> 전혀 안 먹음. 내 생각에는 D.Frame 의 함수의 부분들을 type FrameParam = {}
> 형태로 빼서 공유되는 타입 부분으로 D.Mapper.Frame 도 구성되고, 리턴부분만
> 다르게 될것 같아. Claim 자체는 타입을 받는건 말이 안되어보임. New 와는 완전
> 다른 계열이라서: New 는 후행 입력에 대한 타입을 선언시키는 D 계열이지만,
> Claim 은 공유 부분이고 말한것 처럼 엔진 요소를 알 수 없어. a 가 가장 정확한
> 구현으로 보임.
> 2. 나는 처음에 A 를 생각했어. 권고 그대로 가줘.
> 3. 그런데 호출 순서가 달라진다는 문제가 있긴함. D.Mapper 의 프레임이 전부
> 구워지는 도중에 생기고 각각 claim 매핑 되기 전에 New 가 호출됨. 새로 붙임
> 자체는 한 배열에 섞이는게 문제는 없어보여. 그 경우에서도 순차 마운트 Add 는
> 작동할것이거든. 대신 Add 가 위치를 진짜 실어서 보내지 않으면 맨 뒤에
> 놓인다는게 문제일 뿐.
> 4. 는 디버깅 도구 만들 때 고려해야할 점으로 옮겨져야해. 부분 부분 디버깅
> 가능성을 아직 다 논한게 없어서 지금 그림으로 보면 작은 그림을 먼저
> 그리는거라서, 미결상황으로, 위치 이동이 필요함
> 5. 표면 이름은 Claim 이 가장 마음에 들어. D.Mapper 가 이미 있는걸 매핑해서
> 내가 가진다는 의미적으로 가장 맞고.
> 승격은 지금도 가능해보임. 간단한 레이어라서.
## 에이전트가 확인한 사실과 되물은 것
- `D.Frame = New<<Frame>> "Frame" :: (({ ...타입명시 }) -> Frame)` — props 타입이
캐스트 안 인라인이고 `D` 전체가 생성기 산출물(`base/bind-system-plan.md`) →
`type FrameParam` 공유는 생성기 한 줄. `InstanceChildHandler``v.Parent = inst`이고
`H-154` 문단이 이미 "같은 값, 엔진 no-op"을 전제 → §5-8 근거. `nativeInsert`
Roblox가 offset을 무시(`architecture.md`) → §5-3의 위치 문제는 web 프로바이더 요구사항.
- 제안 둘(사용자 거부 없음, 결정은 아님으로 표시): 센티널을 `D.Mapper.Root`
두기 / `Claim<T>(inst: T, desc: MapperDescriptor<T>): T` **추론**(타입 인자 없음은
사용자 결정).
- **§5-7 문항을 다시 세움** — "여러 quad 인스턴스"가 아니라 **한 quad·여러
스크립트**(같은 `quad` 모듈 require)가 흔한 경우라 이중 claim error가 그 사례를
막는 게 진짜 문제. 갈래 (α) `Claim`은 전체 소유만, 루트 `.Parent =`는 밖에서
(`H-146` 복원, 새 메커니즘 0) [권고] / (β) 다중 claim·부분 소유(web에서 offset이
남의 자식을 못 봄) / (γ) 별도 표면(`H-146` 인용문이 반대한 것).
## 사용자 원문 (2차 메시지) — §5-7·§5-8
> 5-8 확인완료. 5-7 에서는 정확히는 두번 Claim 불가하다는 의미. 필요하다면
> Slot 을 안에 만들고 리턴하는 중간 모듈을 만들어야함. 밖에서 .Parent 설정하는건
> 괜찮아. 루트도 quad 소유이긴 한데, .Parent 를 밖에서 설정하는건 괜찮음.
> 정확히는 ScreenGUI 가 이미 존재해도 똑같음.
읽기: (α) 채택. `Claim`은 같은 `inst`에 1회, 소유는 전체. 루트의 `Parent`
**만든 방법과 무관하게**(`New`든 `Claim`한 기존 `ScreenGui`든) 부기 밖이라 밖에서
대입 허용 — `H-146`의 예외가 좁혀서(그리고 `Claim`한 루트까지 넓혀서) 복원됐다.
PlayerGui 직하 Slot을 여러 스크립트가 공유하려면 `Claim` 한 번 + Slot 반환 중간 모듈.
## 반영
`git mv research/existing-mount-plan.md base/claim-plan.md` 후 전면 재작성(§7 결정
기록, §8 안 만들기로 한 것, §9 구현 체크리스트·문서화 대상). 포인터 갱신:
`bind-system-plan.md` `H-146` 배너("폐기" → "좁혀서 복원") / `slot-plan.md` 두 곳 /
`architecture.md` 주입 op 목록에 `nativeFindChild`(조합 폴백 예외) / `ROADMAP.md`
M2 배너·M5 두 체크박스 / `question.md` 최우선 절 비움 + `archive/question-resolved.md`
절 / `debug-tooling-plan.md` "열린 질문"에 검사 범위 이관 / `documentation-content-map.md`
§4 21번 / `README.md` 행 이동 / `CLAUDE.md`·`todos.md`·`project-context.md` /
`-round10-followup.md` 후속 3 절. 코퍼스의 옛 경로는 새 경로로 일괄 치환(히스토리
문서 포함 — 파일 참조가 깨지지 않게).
## 옛 §5 원문 (승격 직전 커밋 `f020f3f``research/existing-mount-plan.md` §5 — 갈래 a/b/c 선택지 전문)
1. **루트 디스크립터의 이름** — 루트는 `Claim``inst`를 직접 받으니 매칭 키가
필요 없다. 사용자: *"최상위는 이름을 뭐로 둬야할지 아직 모르겠음. 비워두는걸
D.Mapper.Frame{} 으로 제공하는건 더 나빠보이는데. 아니면 테이블로써
MapperRoot = {} Mapper.Frame (MapperRoot) {} 모양이 되어도 될것같음."*
갈래: (a) 센티널 `MapperRoot`(`M.Frame(MapperRoot) {…}`) / (b) 루트는
`Claim(inst, { … })`처럼 클래스 없는 맨 테이블(클래스는 `inst`가 이미 안다)
/ (c) 이름을 받되 무시. **권고 (b)** — 루트 클래스를 두 번 말하지 않고,
`M.<Class>`는 "찾아야 하는 자식"에만 쓰여 뜻이 하나가 된다. 단 타입(`D`
생성기가 만든 props 타입)을 잃으므로 `Claim<<"Frame">>(inst, {…})`처럼
타입 인자로 보완 — `New<<X>>`와 같은 관용구.
2. **물리 순서 계약** — 디스크립터 배열 순서와 기존 트리의 실제 순서가 다를 때.
Roblox는 물리 순서가 의미 없어 무관, web은 DOM 순서라 `nativeInsert` 위치가
어긋난다. 갈래: (a) 디스크립터 순서가 정본이고 일치는 사용자 책임(UB,
debug 검사) / (b) claim 시 quad가 `nativeMove`로 실제 순서를 디스크립터에
맞춘다. **권고 (a)** — "이미 있는 걸 그대로"의 취지, Roblox에선 비용 0.
3. **`New`로 만든 자식을 claim된 부모에 넣는 것** — 위 §4 첫 항목. 정적 자식이니
`InstanceChildHandler``Parent =`를 하면 되고 새 결정은 없어 보이나,
"매핑(이미 있음)"과 "생성(새로 붙임)"이 한 배열에 섞이는 것이 계약상
괜찮은지 확인 문항.
4. **debug 검사의 범위** — 이름 중복 / 부재 / 클래스 불일치 / 미매핑 부기
대상 자식 / 같은 quad의 이중 claim 중 어디까지. 권고: 전부(debug에선 싸다).
5. **표면 이름**`Claim`/`Mount`/`Adopt`, `D.Mapper`/`D.Existing`.
`Mount(root, parent)``H-146`에서 기각한 사유("부기 없는 대상에 quad 객체
주입")는 여기 반대로 적용된다 — claim은 부기를 *세우는* 행위. 권고 `Claim`.
6. **마일스톤** — M5 이후(`nativeFindChild`가 프로바이더 표면). `ROADMAP.md`
백로그에 포인터만. **[2026-08-28 `/code-review`, `H-161`]** 단 `H-146` 루트
예외를 폐기한 지금 **M5에 승인된 루트 부착 경로가 없다** — (a) `Claim`을 M5
스코프로 당김 / (b) `Claim` 전까지 M5 한정 임시 예외 / (c) 루트 컨테이너용
얇은 표면. **사용자 확정 (a)**(*"M5 스코프로 올라가도 될것으로 보임"*) — 헤더와
`ROADMAP.md` M5 체크박스에 반영. §5-7(다중 스크립트)은 여전히 미결.
7. **[2026-08-28 `/code-review`, `H-161`] 여러 스크립트/여러 quad가 같은 루트
컨테이너를 쓰는 경우** — 위 "이중 claim error / 다중 quad UB / 부기 대상 자식
전부 매핑"을 그대로 두면 `Shop.client.luau``Inventory.client.luau`가 각각
`Claim(PlayerGui, …)`하는 **가장 흔한 사례가 막힌다**. 이건 "전부 매핑" 계약이
**루트 컨테이너**(부기 대상이 아닌 `PlayerGui`·`CoreGui`류 — 자식 순서가
의미 없고 quad가 그 형제들을 관리하지 않는다)에는 안 맞는다는 신호다. 갈래:
(a) `Claim`은 **부기를 갖는 노드**에만, 루트 컨테이너엔 "quad가 만든 자식
하나를 붙이는" 별개 표면(부기 없음, 여러 스크립트 공존, 이름은 §5-5와 같이) /
(b) `Claim`에 "이 노드의 다른 자식은 관리하지 않는다"(부분 매핑) 모드 — 단
web처럼 물리 순서가 의미 있는 엔진에선 위험 / (c) 다중 claim을 허용하되 각
claim이 자기가 매핑한 자식만 소유(UB 대신 정의) — 부기 충돌 없음이 조건.
**권고 없음** — (a)는 `H-146`에서 기각한 `Mount(root, parent)`의 재개방과
경계가 얇고, (b)(c)는 계약을 약화시킨다. 사용자 판단.
8. **매핑된 정적 자식의 `Parent` 대입** — §2는 "부기만, `.Parent =`는 안 한다"인데
`InstanceChildHandler``v.Parent = inst`가 계약(`dispatch-core-plan.md`
`H-134`). 같은 핸들러를 쓰면 이미 거기 있는 자식에 같은 값을 재대입(엔진
no-op)하는 것뿐이라 별도 핸들러가 필요 없어 보인다 — 확인 문항(권고: 같은
핸들러, 재대입 감수).
## 승격 뒤 `/code-review high` (2026-08-28)
감사 6라운드 수렴 뒤 돌린 리뷰가 10건을 냈다. 서술·라벨·stale 여섯은 반영, **새
메커니즘이 필요한 넷**(gcconn/gchold 셋업 자리 / 이중 claim 레지스트리 / `PlayerGui`
own-all vs `ResetOnSpawn` / 공유 `Param` 배열 파트)은 `base/claim-plan.md` §10과
`question.md`에 문항으로 — 사용자 결정 대기. 리뷰가 제안한 이름(`nativeAdopt`,
`FrameParam<C>`)은 전부 가칭.

View file

@ -56,11 +56,17 @@
와 함께 **같은 날 대화형으로 전량 결정·반영**(소스 `-round10-followup.md`).
**어제 결정 중 뒤집힌 것 셋**: `fn`/cleanup은 자기 구독을 못 바꾼다(`H-147`, `H-143` 소멸) /
재구독·재바인드의 `Refresh` 캐치업 폐기(`H-151`) / 루트는 밖에서 `.Parent =`
아니라 quad가 `Claim`으로 소유(`H-148`, `research/existing-mount-plan.md`). **같은 날 후속** `H-158`~`H-162`(`:Block` 폐기 → `state:Apply(blocker)` /
아니라 quad가 `Claim`으로 소유(`H-148`, `base/claim-plan.md`). **같은 날 후속** `H-158`~`H-162`(`:Block` 폐기 → `state:Apply(blocker)` /
`_rerunRequired` 홀드 플래그가 `_installed`를 흡수 / `Claim` M5 스코프 / `Void` export)도
확정·반영, **후속 2** `H-163`/`H-164`(`EmitReceive` 인터페이스 — 전파 루프는
`sub:_receive(from)`만 / Slot 재마운트 캐치업 / `emitFrom == nil` 계약 / `Observer:_catchUp`)까지. 남은 미결은 `question.md` 최우선 절의 `Claim` 갈래(특히 §5-7 다중
스크립트) 하나 — 게이트 아님. 남은 액션: M2 착수.**
`sub:_receive(from)`만 / Slot 재마운트 캐치업 / `emitFrom == nil` 계약 / `Observer:_catchUp`)까지. **[2026-08-28 후속 3]** `Claim` 갈래 여덟도 같은 날 전량 확정돼
`research/`에서 **`base/claim-plan.md`로 승격**(결정 기록은 그 §7 — 루트 키 센티널 /
디스크립터 순서 정본 / debug 검사 범위는 `research/debug-tooling-plan.md`로 /
**루트의 `.Parent =`는 밖에서 허용으로 복원**, `Claim`은 1회·전체 소유).
`question.md` 최우선 절은 비어 있다. 승격 뒤 `/code-review high`가 **M5 착수 전
문항 넷**(gcconn/gchold 셋업 자리 / 이중 claim 레지스트리 / `PlayerGui` own-all /
`<Class>Param` 배열 파트)을 냈다 — `base/claim-plan.md` §10·`question.md`, M2 게이트
아님. 남은 액션: M2 착수.**
아래는 돌리기 전(2026-08-26) 서술:
지시서는 `qa-request/pre-implementation-handtrace-round9-brief.md`. 스코프는
**커밋 `9dd8213` 하나의 델타**다 — 8라운드 결정 반영과 그 뒤

View file

@ -16,9 +16,8 @@ M2(반응형 코어 — Source/State/Store)**. **⚠️ [2026-08-24] M2와 M3의
`-round9-followup.md` — Q1~Q10·`H-138`·`H-139`·`H-142`, 그리고 `/code-review`
`H-143`~`H-146`까지 전량 반영 완료**; **[2026-08-28] 10라운드 몫은
`-round10-followup.md`** — 광범위 탐사 `H-150`~`H-157`까지 전량 결정·반영, 그중
`H-143``H-146` 루트 예외는 하루 만에 뒤집힘)이고, `.claude/question.md` 최우선
절엔 `Claim` 갈래(`research/existing-mount-plan.md` §5, 특히 다중 스크립트)만
남아 있다(M2 착수 게이트는 아님). 같은 상태를 `.claude/project-context.md`
`H-143``H-146` 루트 예외는 하루 만에 뒤집힘 — 후자는 같은 날 다시 좁혀 복원)이고, **같은 날 `Claim` 갈래까지 전량 확정돼 `research/`에서
`base/claim-plan.md`로 승격** — `.claude/question.md` 최우선 절은 **비어 있다**. 같은 상태를 `.claude/project-context.md`
서술하니 마일스톤이 넘어갈 때 두 곳을 같이 고칠 것. 진행 상황의 소스는
항상 루트 `ROADMAP.md`.

View file

@ -31,8 +31,10 @@ quad-v2 구현 단계 실행 계획. 설계 근거/아키텍처 자체는 여기
> weak-key / 루트 부착은 사용자 몫)도 같은 날 확정·반영. **[2026-08-28] 10라운드**
> (`-round10-followup.md`가 소스) — 그중 둘은 하루 만에 다시 뒤집혔다: `fn`/cleanup은
> 자기 구독을 못 바꾼다(`H-147`, `H-143` 소멸 · `rawRerun(force)`/`Rerun` 분리) /
> 루트는 밖에서 `.Parent =`가 아니라 **quad가 `Claim`으로 소유**(`H-148`,
> `research/existing-mount-plan.md`, **M5 스코프**`H-161`). 그 밖에 `_epochs`는 emit 때만 갱신
> 이미 있는 트리는 **quad가 `Claim`으로 소유**(`H-148`, `base/claim-plan.md`,
> **M5 스코프**`H-161`; 같은 날 갈래까지 전량 확정 — 그 과정에서 **루트의
> `.Parent =`는 밖에서 허용으로 복원**, 요지는 M5 `Claim` 체크박스; `/code-review`
> 낸 M5 착수 전 문항 넷은 그 문서 §10). 그 밖에 `_epochs`는 emit 때만 갱신
> (`Refresh` 캐치업 폐기, `H-151`) / `Effect._blocker` 제거(`H-150`) / Observer
> 진입점 인라인(`H-149`) / `GateNode` 브랜드 등록(`H-152`) / Store 예약 이름
> 런타임 가드(`H-153`) / `InstanceChildHandler` dedup(`H-154`).
@ -949,6 +951,9 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
> **`onDestroying(inst, fn)`**(`H-11`): `Effect`의 leaf 사망 cleanup을 발화시키는
> 훅으로, `bindLifetime``isEffect`일 때 부른다(quad-roblox 구현은
> `inst.Destroying:Connect(fn)`). 이것도 조합으로 못 만들므로 미주입이면 에러다.
> **[2026-08-28]** 셋째로 **`nativeFindChild(inst, key)`**(`Claim`의 조회 op, 아래
> `Claim` 체크박스·`base/claim-plan.md`) — 같은 예외 취급(에이전트 분류). 전체
> 목록의 소스는 `base/architecture.md`.
> **⚠️ 구현 관례**: `quad-roblox`의 공개 타입은 지금부터 단일 파일
> (`src/init.luau` 또는 `types.luau`)에 몰아둘 것 — 나중에 필요해지면
@ -960,7 +965,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
`QuadRoblox(Quad): QuadRoblox``QuadTypes.CheckedQuad<T, Pattern>`으로
주입받은 quad-base 버전을 확인(`base/quad-types-plan.md` 참고)
- [ ] `D/init.luau`(제네릭 생성자 `New` + 생성기가 찍는 정적 별칭 필드 — **[2026-08-18]** 범위는 "GUI에 쓰이는 모든 인스턴스", 이벤트 필드의 콜백 타입까지 생성, `base/bind-system-plan.md`의 "인스턴스 생성 / 이벤트 네이밍 인체공학" 절). **[2026-08-27 9라운드 `H-142`] 생성되는 props 타입에서 `Parent`를 제외할 것** — props에 `Parent`는 올 수 없다(부모가 하는 일, 같은 문서의 파이프라인 절). `New` ①~④ 순서는 그 절의 의사코드가 소스
- [ ] `Handlers/Property.luau`(**[2026-08-27 9라운드 `H-142`]** `isHandlable``"Parent"` 키를 **거부**한다 — 매치 핸들러가 없어지면 `Dispatch.process`의 "매치 핸들러 없음 → 즉시 error"에 걸리는 것으로 런타임 가드가 공짜로 생긴다, 새 메커니즘 없음. **사용자 확정은 "props에 `Parent` 금지"라는 규칙이고, 이 거부 배선은 에이전트 선택**`base/bind-system-plan.md``H-142` 항목이 그렇게 갈라 적음. **[2026-08-28 10라운드 `H-148`]** 전용 문구는 **철회**(일반 매치 실패 그대로) — 루트는 밖에서 `.Parent =`가 아니라 quad가 `Claim`으로 소유하는 쪽으로(`research/existing-mount-plan.md`, 아래 백로그)), `Handlers/InstanceChild.luau`(**[2026-08-28 `H-154`]** retractor 첫 줄 `if nextValue == v then return end` — 같은 값 재발행 dedup, `SlotHandler` 동형) —
- [ ] `Handlers/Property.luau`(**[2026-08-27 9라운드 `H-142`]** `isHandlable``"Parent"` 키를 **거부**한다 — 매치 핸들러가 없어지면 `Dispatch.process`의 "매치 핸들러 없음 → 즉시 error"에 걸리는 것으로 런타임 가드가 공짜로 생긴다, 새 메커니즘 없음. **사용자 확정은 "props에 `Parent` 금지"라는 규칙이고, 이 거부 배선은 에이전트 선택**`base/bind-system-plan.md``H-142` 항목이 그렇게 갈라 적음. **[2026-08-28 10라운드 `H-148`]** 전용 문구는 **철회**(일반 매치 실패 그대로). 루트 부착 경로가 무엇인지는 아래 `Claim` 체크박스가 소스), `Handlers/InstanceChild.luau`(**[2026-08-28 `H-154`]** retractor 첫 줄 `if nextValue == v then return end` — 같은 값 재발행 dedup, `SlotHandler` 동형) —
**⭐ [2026-08-27 9라운드 `H-134`] `InstanceChildHandler`도 말단이라
부기를 등록한다**: `process`에서 `setOffsetSource(inst, k, None)`
`v.Parent = inst``setLength(inst, k, 1, inst)`(정적 단일 자식은 상수
@ -971,7 +976,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
문단. 빠뜨리면
`Frame { Frame{}, Slot() }`이 첫 마운트에서 죽는다 —
`base/dispatch-core-plan.md``H-39` 블록(그 다섯째 항목)이 소스.
- [ ] **[2026-08-28 M5 스코프, `H-161`]** `Claim(inst, D.Mapper.<Class> "Name" {…})` — 이미 있는 트리(PlayerGui·`Clone()` 사본)를 quad가 소유. `H-146` 루트 예외를 폐기한 뒤 M5에 승인된 루트 부착 경로가 이것뿐이라 **백로그가 아니라 M5 안**(사용자 확정). 프로바이더 op `nativeFindChild` 필요. 갈래 미결(특히 §5-7 다중 스크립트/루트 컨테이너) — `research/existing-mount-plan.md` §5가 소스(개수도), 다음 배치 문항.
- [ ] **[2026-08-28 M5 스코프, `H-161`; 같은 날 갈래 전량 확정]** `Claim(inst, D.Mapper.<Class>(key) {…}) -> inst` — 이미 있는 트리(PlayerGui·`Clone()` 사본·Studio GUI)를 quad가 소유(`base/claim-plan.md`가 소스, 구현 체크리스트는 그 §9). 요지: `drive` 위의 한 겹(DFS 이름 해석 → bottom-up `drive`), 매핑된 자식은 `InstanceChildHandler` 그대로(별도 핸들러 없음), 루트 키 센티널 `D.Mapper.Root`, `type <Class>Param``D.<Class>`와 공유, `Claim`은 타입 인자 없음, 같은 `inst` 이중 claim error, `Processed` 소진. 프로바이더 op **`nativeFindChild(inst, key)`**(조회라 조합 폴백 예외 — 미주입이면 error). **루트 부착의 흔한 경로는 이게 아니라 밖에서 `.Parent =`** — 루트의 `Parent`는 부기 밖이라 허용(`base/claim-plan.md` §5가 소스; 10라운드 `H-148`에서 한때 "루트도 `Claim`으로만"으로 폐기됐다가 같은 날 복원). **⚠️ 착수 전 사용자 문항 넷**(gcconn/gchold 셋업 자리 / 이중 claim 레지스트리 / `PlayerGui` own-all / `<Class>Param` 배열 파트) — `base/claim-plan.md` §10. `D/init.luau` 생성기는 `type <Class>Param`(필드 파트)을 `D.<Class>`·`D.Mapper.<Class>`가 공유하도록 찍는다.
- [ ] **Instance 생성 시점의 gcconn/gchold 셋업**(2026-08-14 다섯 번째 세션
확정, 옛 "`bindLifetime` 첫 호출에서 lazy 생성"에서 전환 — `base/
lifecycle-pattern.md`의 "(0) gcconn/gchold는 Instance 생성 시점에