- 루트 키 센티널(맨 테이블+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
26 KiB
이미 있는 트리를 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— 프로바이더 opnativeFindChild가 필요하니 프로바이더 마일스톤이 자연스러운 자리). 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. 모양
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이 그 하나를 쓴다 — 리턴만 다르다 (FramevsMapperDescriptor<Frame>). 사용자: "D.Frame 의 함수의 부분들을 type FrameParam = {} 형태로 빼서 공유되는 타입 부분으로 D.Mapper.Frame 도 구성되고, 리턴부분만 다르게".base/bind-system-plan.md의D.Frame = New<<Frame>> "Frame" :: ((…) -> Frame)캐스트가 인라인 타입 대신 이 이름을 쓰게 되는 것뿐이고,D가 전량 생성기 산출물이라 손으로 쓸 곳은 없다.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.mdM5D/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)의 한계 사례다 — RobloxPlayerGui는 엔진이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 번호 그대로.
- 루트 디스크립터의 키 — (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 검사 후보. - 물리 순서 — (a) 디스크립터 순서가 정본, 일치는 사용자 책임. 사용자:
"나는 처음에 A 를 생각했어. 권고 그대로 가줘." (b)(
nativeMove로 quad가 맞춤)는 기각. - claim된 부모 안의
New자식 — 허용. 사용자: "새로 붙임 자체는 한 배열에 섞이는게 문제는 없어보여. 그 경우에서도 순차 마운트 Add 는 작동할것이거든." 지적한 순서·위치 문제는 §6 마지막 항목(프로바이더 요구사항)으로. - debug 검사의 범위 — 여기서 정하지 않고
research/debug-tooling-plan.md로 이동. 사용자: "디버깅 도구 만들 때 고려해야할 점으로 옮겨져야해. 부분 부분 디버깅 가능성을 아직 다 논한게 없어서 지금 그림으로 보면 작은 그림을 먼저 그리는거라서, 미결상황으로, 위치 이동이 필요함". 이 문서엔 "debug 검사가 있다"(§3)만 남는다. - 표면 이름 —
Claim+D.Mapper. 사용자: "표면 이름은 Claim 이 가장 마음에 들어. D.Mapper 가 이미 있는걸 매핑해서 내가 가진다는 의미적으로 가장 맞고."Mount/Adopt/D.Existing기각. - 마일스톤 — M5(
H-161, 헤더). - 여러 스크립트가 한
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는 만든 방법과 무관하게 부기 밖. - 매핑된 정적 자식의
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-updrive, 소진 플래그(§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.mdM5 체크박스가 진행의 소스.- 문서화 대상(
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-robloxNew②단계의 인라인 코드(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-instInstData에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번 — "추론만으로 … 확정하지 말 것").