- H-158 state:Block → state:Apply(blocker), Blocker:__apply 메소드형(호출 규약 명시) - H-159 사용자 제안 _rerunRequired 홀드: fire=Update→Rerun, rawRerun이 실행 불가 상태의 요청을 홀드, _installed 폐기, Observer 대칭(전파 루프 else + bind/subscribe 1회 발화, 생성자 순서 fn→_subs) - H-160 홀드로 정정 + "error 나면 그 Effect는 죽는다" 계약 / H-161 Claim M5 스코프 / H-162 Void.luau 잎 모듈 - 감사 7→6→1→0, /code-review high 10건 중 8 반영, 둘(H-163 Slot 내부 Observer×홀드, H-164 emitFrom nil)은 문항으로 Co-authored-by: qwreey <me@qwreey.moe> Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
12 KiB
이미 있는 트리를 quad가 소유하기 — Claim + D.Mapper (가칭)
[2026-08-28 신설, 사용자 발의] 10라운드
H-148(Parent거부 문구)을 논의하다 더 큰 표면의 공백이 드러나 만든 문서. 상태: 설계 논의 중 — 방향은 사용자 확정, 갈래 몇 개 미결(§5). M2 착수 게이트 아님, M5 스코프 ([2026-08-28H-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. 모양 (사용자 스케치 + 확정된 것)
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. 미결 — 다음 배치 문항
- 루트 디스크립터의 이름 — 루트는
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>>와 같은 관용구. - 물리 순서 계약 — 디스크립터 배열 순서와 기존 트리의 실제 순서가 다를 때.
Roblox는 물리 순서가 의미 없어 무관, web은 DOM 순서라
nativeInsert위치가 어긋난다. 갈래: (a) 디스크립터 순서가 정본이고 일치는 사용자 책임(UB, debug 검사) / (b) claim 시 quad가nativeMove로 실제 순서를 디스크립터에 맞춘다. 권고 (a) — "이미 있는 걸 그대로"의 취지, Roblox에선 비용 0. New로 만든 자식을 claim된 부모에 넣는 것 — 위 §4 첫 항목. 정적 자식이니InstanceChildHandler가Parent =를 하면 되고 새 결정은 없어 보이나, "매핑(이미 있음)"과 "생성(새로 붙임)"이 한 배열에 섞이는 것이 계약상 괜찮은지 확인 문항.- debug 검사의 범위 — 이름 중복 / 부재 / 클래스 불일치 / 미매핑 부기 대상 자식 / 같은 quad의 이중 claim 중 어디까지. 권고: 전부(debug에선 싸다).
- 표면 이름 —
Claim/Mount/Adopt,D.Mapper/D.Existing.Mount(root, parent)를H-146에서 기각한 사유("부기 없는 대상에 quad 객체 주입")는 여기 반대로 적용된다 — claim은 부기를 세우는 행위. 권고Claim. - 마일스톤 — M5 이후(
nativeFindChild가 프로바이더 표면).ROADMAP.md백로그에 포인터만. [2026-08-28/code-review,H-161] 단H-146루트 예외를 폐기한 지금 M5에 승인된 루트 부착 경로가 없다 — (a)Claim을 M5 스코프로 당김 / (b)Claim전까지 M5 한정 임시 예외 / (c) 루트 컨테이너용 얇은 표면. 사용자 확정 (a)("M5 스코프로 올라가도 될것으로 보임") — 헤더와ROADMAP.mdM5 체크박스에 반영. §5-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)는 계약을 약화시킨다. 사용자 판단. - 매핑된 정적 자식의
Parent대입 — §2는 "부기만,.Parent =는 안 한다"인데InstanceChildHandler는v.Parent = inst가 계약(dispatch-core-plan.mdH-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.