qa: Claim 후속 문항 넷 확정 — nativeClaim 주입 op(gcconn/gchold 경로 전부), 이중 claim은 셋업 유무로, PlayerGui는 claim 대상 아님, <Class>Param<E>
- base/claim-plan.md §7-9~12 + §10 해소 표시, §6 Claim(PlayerGui) 예시 폐기(루트는 ScreenGui/SurfaceGui) - architecture.md 주입 op 목록 nativeClaim / bind-system-plan New ② 주석 / lifecycle-pattern (0) 머리 / slot-plan 폴백 예외 - ROADMAP M5(gcconn 셋업 체크박스·Claim 체크박스·배너), question.md M5 절 삭제, archive/question-resolved 추기 - 감사 1라운드(2건) 반영 Co-authored-by: qwreey <me@qwreey.moe> Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
This commit is contained in:
parent
253d141096
commit
51ffadcd8d
14 changed files with 144 additions and 103 deletions
|
|
@ -72,7 +72,7 @@
|
|||
| `fallback-plan.md` | **[2026-08-14 세션, `research/`에서 승격]** `Fallback`/`Traceback` — 컴포넌트 함수를 감싸 에러 시 플레이스홀더를 그려주는 순수 슈가(`additional-primitives-plan.md`의 "Error Boundary" 절이 내린 "빈 자리 아님" 결론 위에 얹힘). `Fallback`은 `pcall` 기반(trace 없음), `Traceback`은 `xpcall`+`debug.traceback` 기반(trace 항상 있음) — 플래그 대신 별도 함수로 분리(`Ref`/`PreRef`와 같은 패턴). `err: any`(Lua `error()`가 임의 값을 던질 수 있음, `error(msg)` 기본 호출의 위치 접두 캐비엇 포함) 확정. 패키지는 `quad-base`, 이름 확정. 메커니즘 실측은 `audit/fallback-xpcall-verification.md`. 구현 우선순위는 형제 백로그(`quad-mock`/`quad-debug`/`Operator`)와 동급, 맨 뒤 **⚠️ [2026-08-24 6라운드 `H-26`] 미해결 항목이 하나 신설됐다** — **실패 이전에 생성된 부분 트리는 회수되지 않는다.** quad Instance는 gcconn 때문에 `Destroy`로만 회수되는데, 컴포넌트가 리터럴을 만들다 던지면 그때까지 완성된 형제/자손이 트리에 붙지도 파괴되지도 않은 채 사라지고 `Fallback`은 그 존재를 알 방법이 없다. **`Fallback`/`Traceback`이 그 경로를 계속 살려두는 걸 존재 이유로 삼는 대표 사용처**라 층위가 다르다 — **백로그**(그 둘이 슈가라 구현 시점에 같이 다룬다, 그 문서의 ⚠️ 절이 소스) |
|
||||
| `lifecycle-hooks-plan.md` | **[2026-08-14 아홉 번째 세션, `research/`에서 승격]** 생명주기 훅 슈가 `OnCreated`/`OnRendered`/`OnDestroyed` — 각각 `PreRef():Callback(fn)`/`PostRef():Callback(fn)`/`Effect(function() return fn end)`를 반환하는 **순수 팩토리 함수**라 새 타입/Dispatch 개념이 전혀 안 생김(호출 즉시 평가돼 기존 인스턴스로 사라짐), 여러 개 나란히 등록도 자연 지원(단 **같은 계열끼리의 순서는 미보장**). 마지막 열린 항목이던 `OnRendered`는 사용자가 **채택 확정** — 메커니즘은 `PostRef`(`base/ref-plan.md`), 원래 열어뒀던 (a)/(b)/(c) 중 **(a)**. 캐비엇: `OnRendered`는 서브트리 완성은 보장하지만 **이 인스턴스가 부모에 붙기 전**에 불림(React `componentDidMount`와 다름) — 문서화 필수. 패키지 `quad-base` 확정. **[2026-08-14 열 번째 세션]** `dispose()` 범위(0-B)가 `Slot`+`Instance`로 좁혀지고 `Observer`/`Effect`는 제외되는 쪽으로 확정되며 `OnDestroyed` 이름 재검토 조건이 발동 없이 종결 — `OnDestroyed`가 최종 이름, 용어 대기열에서도 제외 **⚠️ [2026-08-26, 8라운드 `H-120`] 실제로는 `Callback(guard(fn))`이다** — `Ref` 콜백의 *"등록 즉시 1회, 값이 nil이어도"* 계약 때문에 맨 `fn`을 걸면 **생성 시점에 `fn(nil)`이 먼저 불려** `inst`를 바로 쓰는 콜백이 pre-pass에 닿기도 전에 죽는다. `guard(fn) = function(v) if v ~= nil then fn(v) end end`. `Ref` 계약은 안 건드리고 슈가 쪽에서 막는다. |
|
||||
| `gate-plan.md` | **[2026-08-21 신설, 같은 날 표면 확정]** `state:Gate(setup)` — 상류 emit을 가로채 내려보낼지 정책이 정하는 **`GateNode`**(`ComputeNode`와 같은 층위)를 만드는 State 메소드. 탑레벨 `Gate(...)` 프리미티브는 **안 만든다**(처음 방향에서 뒤집힘) — `Blocker`가 `state:Apply(blocker)` 안에서 이 배선을 쓰고, `Debounce`/`Throttle`은 `state:Apply(...)` 팩토리가 내부에서 `:Gate`를 부른다. `Get()`엔 영향 없음(통지만 막음)까지 확정. **[2026-08-24 6라운드 `H-33`/`H-49`] 열린 항목이 전부 닫혔다** — 재진입은 2026-08-21에 이미 닫혀 있었고, 마지막 남은 생명주기(=`Gate`에 `Flush`/`Cancel` 표면을 둘지)는 **안 두는 것**으로 확정: `blocker:Policy(emit)`을 노출하고 `Debounce`/`Throttle`이 자기 `Blocker`를 조종하는 정책이 된다(정책 합성은 손으로 중첩). (**[2026-08-22]** 미결이던 "마일스톤 범위 — `Gate`만 vs `Blocker`까지"는 **둘 다 같은 마일스톤**으로 해소.) 구현은 M2 |
|
||||
| `claim-plan.md` | **[2026-08-28 신설·같은 날 확정, `research/`에서 승격]** 이미 있는 트리(PlayerGui, `Clone()` 사본, Studio에서 만든 GUI)를 quad가 **소유**하는 `Claim(inst, D.Mapper.<Class>(key) {…}) -> inst` — 루트가 Slot일 수 없던 공백(`H-146`/`H-148`)을 `drive` 위의 한 겹(DFS 이름 해석 → bottom-up `drive`)으로 닫는다. 계약은 claim-once·own-all(부기 대상 자식 전부 매핑, 디스크립터 순서가 정본, 이름 중복·부재 UB + debug 검사, 같은 `inst` 이중 claim error, 다중 quad UB). 루트 키는 센티널 `D.Mapper.Root`, props 타입은 `D.<Class>`와 `type <Class>Param` 공유, `Claim`은 타입 인자 없음(추론). **루트의 `.Parent`는 부기 밖이라 밖에서 대입 허용**(`H-146` 예외를 좁혀 복원 — 여러 스크립트가 한 `PlayerGui`를 쓰는 흔한 경우의 답, PlayerGui 직하 Slot 공유는 중간 모듈). `archive/existing-instance-bind-rejected.md`와 다름(재바인드 아님). 프로바이더 op `nativeFindChild`. **M5 스코프**(`H-161`) |
|
||||
| `claim-plan.md` | **[2026-08-28 신설·같은 날 확정, `research/`에서 승격]** 이미 있는 트리(PlayerGui, `Clone()` 사본, Studio에서 만든 GUI)를 quad가 **소유**하는 `Claim(inst, D.Mapper.<Class>(key) {…}) -> inst` — 루트가 Slot일 수 없던 공백(`H-146`/`H-148`)을 `drive` 위의 한 겹(DFS 이름 해석 → bottom-up `drive`)으로 닫는다. 계약은 claim-once·own-all(부기 대상 자식 전부 매핑, 디스크립터 순서가 정본, 이름 중복·부재 UB + debug 검사, 같은 `inst` 이중 claim error, 다중 quad UB). 루트 키는 센티널 `D.Mapper.Root`, props 타입은 `D.<Class>`와 `type <Class>Param` 공유, `Claim`은 타입 인자 없음(추론). **루트의 `.Parent`는 부기 밖이라 밖에서 대입 허용**(`H-146` 예외를 좁혀 복원 — 여러 스크립트가 한 `PlayerGui`를 쓰는 흔한 경우의 답, PlayerGui 직하 Slot 공유는 중간 모듈). `archive/existing-instance-bind-rejected.md`와 다름(재바인드 아님). 프로바이더 op `nativeFindChild` + **`nativeClaim`**(gcconn/gchold 셋업의 유일한 자리 — `New`도 호출; 이중 claim은 그 셋업 유무로 error). **`PlayerGui`류 공동 소유 컨테이너는 claim 대상 아님**(루트는 `ScreenGui`·`SurfaceGui`). `type <Class>Param<E>` 원소 타입 파라미터. **M5 스코프**(`H-161`) |
|
||||
| `state-epoch-plan.md` | **[2026-08-21 신설, 같은 날 채택 확정·`Epoch` 일반화까지 반영]** State의 재계산/전파 판정을 `invalid` 플래그가 아니라 **`Epoch` 리비전 비교**로 한다 — DFS 전파 도중 `Get()`이 섞인 값을 캐시하던 glitch(실재)를 없애는 **정확성** 결정. `type Epoch = { Revision: number }`(그 자체로 키가 되는 unique 테이블, `Source`가 구조적으로 만족), 부기는 재사용 가능한 **`EpochMap`**(`:Update(Epoch|EpochSet) -> boolean`이 "뒤로 전파가 필요한가"를 답함, `:Refresh`/`:Sync`/`:TrackFrom`. `EpochSet = {[Epoch]: true}`로 **배열이 아니라 집합** — 게이트 배치가 그 모양이다)으로 떼어냈고, State는 그걸 **둘** 컴포지션한다 — `valueEpochMap`(값 유효성)/`emitEpochMap`(전파 dedup). emit은 값도 리비전도 안 싣고 **출처(`Epoch`나 그 집합)만** 싣고, 순회는 **캐시 카운터가 같을 때만** 돌며 값만 앞당기고(**[2026-08-25 `H-85`]** 옛 `rawInvalid` 불린은 `cacheTargetCount`/`cacheCurrCount` 쌍으로 교체 — 재계산 *도중* 도착한 무효화를 꼬리가 지우던 것과, `fn`이 던졌을 때 계산된 적 없는 캐시를 유효하다고 확신하던 것 둘을 같이 닫음) 통지는 상류 emit을 기다린다. 중복 *통지*도 같이 접히므로 `source-state-plan.md`의 옛 "항상 전파 / 중복 통지는 안 접음" 서술이 역전됨(`archive/always-propagate-no-dedup-superseded.md`). ⚠️ 2026-08-14에 폐기된 `invalid` 기반 dedup과는 다른 장치 — 그 금지는 유효. 리비전 갱신은 **`bit32.bnot(-rev)`** 한 번(사용자 확정 — 랩어라운드 **감소**를 단일 FASTCALL로, hot path라 값을 uint32에 가두고 `2^53` 포화 자체를 없앰. **[2026-08-22 정정]** 한때 `band(rev + 1, mask)`로 잘못 옮겨져 있었음). **열린 설계 항목 없음.** **[2026-08-24 재확정]** 구현 마일스톤은 전부 **M2**다 — 2026-08-22엔 `GateNode`가 디스패치 쪽에 있어 `EpochMap.luau`/`Epoch` 인터페이스만 갈려 있었으나, 마일스톤 순서 교체로 그 분리가 없어졌다 |
|
||||
|
||||
## `reference/` — 온디맨드 참고 자료 (2026-08-07 신설)
|
||||
|
|
|
|||
|
|
@ -1305,3 +1305,10 @@ L2 디스패치 Handler · Dispatch 코어 · chains · None · ┐
|
|||
`.Parent =`는 밖에서 허용으로 복원**(사용자: *"정확히는 두번 Claim 불가하다는 의미.
|
||||
필요하다면 Slot 을 안에 만들고 리턴하는 중간 모듈을 만들어야함. 밖에서 .Parent
|
||||
설정하는건 괜찮아"*) / 매핑된 정적 자식은 같은 `InstanceChildHandler`.
|
||||
|
||||
같은 날 저녁 `/code-review high`가 낸 **M5 착수 전 문항 넷**(gcconn/gchold 셋업 자리 /
|
||||
이중 claim 레지스트리 / `PlayerGui` own-all / `<Class>Param` 배열 파트)도 **몇 시간 안에
|
||||
답이 와서 닫혔다** — `base/claim-plan.md` §7-9~12: 주입 op `nativeClaim(inst)`에 (0)
|
||||
경로 전부(`New`도 호출) / 이중 claim은 셋업 유무로 error, 문항의 `elementOwner` 충돌은
|
||||
"claim은 slot과 무관"이라 틀린 전제 / `PlayerGui`는 *"공동 소유 가능 객체"*라 claim
|
||||
대상이 아니고 루트는 `ScreenGui`·`SurfaceGui` / `FrameParam<E>` 원소 타입 파라미터.
|
||||
|
|
|
|||
|
|
@ -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`와 같은 취급) **[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`처럼 **조합 폴백의 예외**(미주입이면 명확한 에러 — 이 분류는 에이전트 판단, 사용자 확정은 "필요 핸들을 프로바이더에 남긴다"까지)
|
||||
├── 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` §7-9] `nativeClaim(inst)`** — `lifecycle-pattern.md` (0)의 gcconn/gchold 셋업(userdata 동일성 고정 + `InstData:SetWeak`)의 **유일한 자리**. `New`의 ②단계와 `Claim`(해석한 inst마다) 둘 다 이걸 부른다. 사용자 확정은 op 신설과 "경로를 여기에 전부"까지(*"nativeClaim 을 만들고 gchold/gcconn 경로를 여기에 전부"*); "셋업이라 조합 불가 → 조합 폴백의 예외"는 `nativeFindChild`와 같이 에이전트 분류. **`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`)
|
||||
|
|
|
|||
|
|
@ -177,9 +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.
|
||||
**[2026-08-28 `Claim`]** 그 `{ ...타입명시 }`는 생성기가 `type <Class>Param<E>`로
|
||||
이름 붙여 찍고 `D.Mapper.<Class>`와 공유한다 — 필드 파트는 같고 children 배열의
|
||||
원소 타입 `E`만 파라미터(`D.Frame`은 기존 유니언, 매퍼는 `| MapperDescriptor`).
|
||||
사용자 확정, `base/claim-plan.md` §2·§7-12.
|
||||
|
||||
- **이름은 대문자 `New`로 통일**(사용자 확정: *"2. New입니다."*) — PA님 코드
|
||||
인용의 소문자 `new`와 섞여 있던 것을 정리.
|
||||
|
|
@ -228,7 +229,8 @@ local function New(className: string)
|
|||
-- ③④보다 앞인 이유 — 거기서부터 `inst`를 키로 쓰는 `Relate`
|
||||
-- (`elementOwner`/`nameClaims`/`bk`/`chains`)가 생기는데, 키의 동일성
|
||||
-- 고정이 그보다 먼저여야 한다.
|
||||
-- (lifecycle-pattern.md (0)의 인라인 코드 — 헬퍼 이름은 구현 시)
|
||||
-- [2026-08-28 `Claim` §7-9] 인라인이 아니라 주입 op `nativeClaim(inst)` 호출 —
|
||||
-- (0)의 코드는 그 op 안에만 산다(`Claim`도 같은 op를 부른다, `base/claim-plan.md`).
|
||||
|
||||
-- ③ flatten — Modifier 항목을 제자리에서 `ProcessedModifier`로 소진하고
|
||||
-- 필드를 해시 파트로 merge. 새 테이블 없음, `inst`를 안 받는 순수 변환
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ local cloned = Claim(template:Clone(), M.Frame(M.Root) { -- 루트는 이름
|
|||
쓰게 되는 것뿐이고, `D`가 전량 생성기 산출물이라 손으로 쓸 곳은 없다.
|
||||
|
||||
```luau
|
||||
type FrameParam = { … } -- 생성기 산출물 (필드 파트)
|
||||
D.Frame :: (FrameParam) -> Frame
|
||||
D.Mapper.Frame :: (key: string | MapperRoot) -> (FrameParam) -> MapperDescriptor
|
||||
type FrameParam<E> = { [number]: E, … } -- §7-12: 원소 타입이 파라미터
|
||||
D.Frame :: (FrameParam<NewChild>) -> Frame -- NewChild = 기존 children 유니언
|
||||
D.Mapper.Frame :: (key: string | MapperRoot) -> (FrameParam<NewChild | MapperDescriptor>) -> MapperDescriptor
|
||||
Claim :: <T>(inst: T, desc: MapperDescriptor) -> T -- T는 inst에서 그대로
|
||||
```
|
||||
|
||||
|
|
@ -70,8 +70,10 @@ local cloned = Claim(template:Clone(), M.Frame(M.Root) { -- 루트는 이름
|
|||
children 배열엔 `MapperDescriptor`가 올 수 없고(오면 런타임 "매치 핸들러 없음"),
|
||||
매퍼의 배열엔 와야 한다(§2 예시의 `M.TextLabel "Title" {…}`). 사용자 인용은
|
||||
**필드 파트의 타입 공유**를 승인한 것이고 배열 파트를 어떻게 가를지는 정하지
|
||||
않았다 → §10-D. `base/bind-system-plan.md`의 `D` 생성기 절엔 아직 `<Class>Param`이
|
||||
없다 — 생성기 구현(`ROADMAP.md` M5 `D/init.luau`)이 이 문서를 같이 본다.
|
||||
않았다 → **[같은 날 확정, §7-12] 원소 타입을 파라미터로**: `type FrameParam<E> =
|
||||
{ [number]: E, …필드 }`, `D.Frame`은 `E` = 기존 children 원소 유니언(Instance·Slot·
|
||||
State…), `D.Mapper.Frame`은 거기에 `| MapperDescriptor`. `base/bind-system-plan.md`의
|
||||
`D` 생성기 절엔 포인터만 — 생성기 구현(`ROADMAP.md` M5 `D/init.luau`)이 이 문서를 본다.
|
||||
|
||||
- **`Claim(inst, descriptor) -> inst`가 최상위이고 타입 인자를 받지 않는다.**
|
||||
사용자: *"Claim 자체는 타입을 받는건 말이 안되어보임. New 와는 완전 다른
|
||||
|
|
@ -84,7 +86,7 @@ local cloned = Claim(template:Clone(), M.Frame(M.Root) { -- 루트는 이름
|
|||
같은 취급). 디스크립터 클래스 ↔ `inst` 클래스 대조는 **debug 검사**(§3)의 몫.
|
||||
`inst`는 quad 밖에서 온 것. 이 호출로 `inst`와
|
||||
매핑된 하위 전부가 **quad 소유**가 된다 — `New`가 만든 것과 같은 gcconn/gchold·부기
|
||||
(그 셋업을 누가 하는지는 §10-A).
|
||||
(그 (0) 셋업은 아래 `nativeClaim` — §7-9).
|
||||
- **디스크립터는 1회용** — `PreRef`의 `_fired`처럼 **디스크립터 객체에 소진
|
||||
플래그**를 세우고 재사용이면 `error(…, 2)`. **[2026-08-28 `/code-review` 정정]**
|
||||
`PreRef` 관용구의 다른 절반(배열 슬롯을 `ProcessedPreRef` 센티널로 교체)은
|
||||
|
|
@ -93,7 +95,16 @@ local cloned = Claim(template:Clone(), M.Frame(M.Root) { -- 루트는 이름
|
|||
테이블을 in-place로 바꿀지 새 테이블을 만들지(그러면 `ProcessedModifier` 자리와
|
||||
인덱스가 `New`와 달라진다)와 Modifier 필드 안에 숨은 디스크립터를 DFS가 보는지는
|
||||
**구현 시 정할 것**(§9). **같은 `inst`를 두 번 `Claim`하는 것도 error**(§7-7) —
|
||||
어느 레지스트리로 판정하는지는 §10-B.
|
||||
판정은 위 `nativeClaim` 항목(§7-10).
|
||||
- **⭐ [2026-08-28 후속, §7-9] 소유는 프로바이더 주입 op `nativeClaim(inst)`** —
|
||||
`lifecycle-pattern.md` (0)의 gcconn/gchold 셋업(클로저가 `gchold`와 `inst`를 캡처해
|
||||
userdata 동일성을 고정하고 `InstData:SetWeak`)이 **이 op 안에만** 산다. `New`의
|
||||
②단계도 인라인이 아니라 같은 op를 부른다(`base/bind-system-plan.md`). `Claim`은
|
||||
DFS로 해석한 inst마다(루트 포함) `drive` **앞에** `nativeClaim`을 부른다 — ②가
|
||||
③④보다 앞인 것과 같은 이유(그 뒤부터 `inst`를 키로 쓰는 `Relate`가 생긴다).
|
||||
**이미 quad 데이터가 있는 inst**(`InstData:GetWeak(inst, "gchold") ~= nil` — 앞서
|
||||
claim됐거나 `New`가 만든 것)면 `error(…, 2)` — 이것이 "같은 `inst` 이중 claim
|
||||
error"의 전부이고 별도 레지스트리는 없다(§7-10).
|
||||
- **매칭은 프로바이더 주입 op** `nativeFindChild(inst, key)`(가칭) — Roblox는
|
||||
`Name`, web은 id/selector. **quad-base가 순회·부기 전반을 구현하고
|
||||
프로바이더는 이 핸들만 낸다**(사용자: *"quad-base 에서 전반을 구현해주고
|
||||
|
|
@ -171,26 +182,21 @@ derive 를 걸어야해. 이건 derive 에선 구현하지 않고, 그 위의
|
|||
거부 배선의 에러 문구는 일반 매치 실패 그대로(`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(엔진이 자식을 넣는 컨테이너)의 답에 걸린다.
|
||||
(PlayerGui는 붙이는 자리일 뿐 claim 대상이 아니다 — §7-11).
|
||||
**`PlayerGui` 자체는 claim 대상이 아니다**(§7-11 — 공동 소유 컨테이너). 여러
|
||||
스크립트가 한 `Slot`을 공유해야 하면 **`ScreenGui` 하나를 만들거나 claim하고 그 안에
|
||||
Slot을 만들어 반환하는 중간 모듈**을 둔다(사용자: *"정확히는 두번 Claim 불가하다는
|
||||
의미. 필요하다면 Slot 을 안에 만들고 리턴하는 중간 모듈을 만들어야함"* — 그 모듈의
|
||||
루트가 `ScreenGui`인 것이 §7-11의 따름).
|
||||
|
||||
## 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`류 컨테이너가 들어가는지도 거기서.
|
||||
- **루트**: `Claim(existingScreenGui, M.ScreenGui(M.Root) { Slot {…} })` — Studio에서
|
||||
만들어 둔 `ScreenGui`(또는 `SurfaceGui`·`BillboardGui`)를 quad 소유 부모로 삼아 그
|
||||
아래 Slot을 둔다. **`PlayerGui`/`CoreGui`류 공동 소유 컨테이너는 claim 대상이
|
||||
아니다**(§7-11 — 엔진이 `StarterGui`를 리스폰마다 복제해 넣고 여러 스크립트가
|
||||
나눠 쓰는 자리라 "소유"가 성립하지 않는다). 그런 컨테이너엔 §5의 `.Parent =`로
|
||||
붙일 뿐이다. 처음 스케치의 `Claim(PlayerGui, M.PlayerGui(M.Root) {…})`는 **폐기**.
|
||||
- **템플릿 대량 생성**: `template:Clone()` → `Claim` — 각 사본이 독립 소유.
|
||||
Claim이 Instance를 돌려주므로 **Slot 요소로도 그대로 쓸 수 있다**(요소는
|
||||
`inst`) — "요소가 너무 많은 경우"의 답.
|
||||
|
|
@ -255,6 +261,33 @@ derive 를 걸어야해. 이건 derive 에선 구현하지 않고, 그 위의
|
|||
8. **매핑된 정적 자식의 `Parent` 대입 — 같은 핸들러, 재대입 감수.** 사용자:
|
||||
*"5-8 확인완료."* 근거는 §4.
|
||||
|
||||
**[2026-08-28 후속 — 승격 뒤 `/code-review high`가 낸 문항 넷(옛 §10 A~D)의 답]**
|
||||
|
||||
9. **gcconn/gchold 셋업 자리 — 프로바이더 op `nativeClaim(inst)`, (0) 경로는 거기에만.**
|
||||
사용자: *"nativeClaim 을 만들고 gchold/gcconn 경로를 여기에 전부 두면 되지 않을까
|
||||
생각중."* "전부"이므로 `New` ②단계의 인라인 코드도 이 op 호출로 바뀐다(에이전트
|
||||
읽기 — `New`가 다른 경로를 따로 가지면 "전부"가 아니다). 리뷰 갈래 (b) `Claim`
|
||||
본체를 프로바이더로 / (c) (0)을 quad-base로는 안 씀. 이름 `nativeAdopt`(리뷰 가칭)
|
||||
폐기.
|
||||
10. **이중 claim — 레지스트리 없음, "이미 quad 데이터가 있는 inst"면 error.** 사용자:
|
||||
*"정확히는, claim 은 slot 이랑 무관하지 않아? 이중 claim 자체가 무슨 상황이야."*
|
||||
— 리뷰가 세운 `elementOwner`(Slot 소유권) 충돌은 **문항 자체가 틀린 것**: claim은
|
||||
Slot 요소 소유권과 다른 축이다. 이중 claim이 실제로 뜻하는 상황은 둘 — 같은
|
||||
inst를 `Claim`에 두 번 넣는 것, 그리고 `New`가 만든(이미 quad 소유인) inst를
|
||||
claim하는 것. 둘 다 9번의 셋업이 이미 있다는 사실 하나로 판정된다(§2).
|
||||
11. **`PlayerGui`는 claim 대상이 아니다 — 공동 소유 컨테이너.** 사용자: *"애초에
|
||||
PlayerGui 자체를 Own 한다는게 좀 잘못되었어. 공동 소유 가능 객체인데 그러는거지.
|
||||
ScreenGui/SurfaceGui 등으로 생각해야지."* own-all 계약(§3)은 손대지 않고 **대상
|
||||
정의**가 답이다: claim은 배타 소유가 성립하는 요소(`ScreenGui`·`SurfaceGui`·
|
||||
`BillboardGui`·`Frame`류·`Clone()` 사본)에만. 리뷰 갈래 (a) "UB로 명문화"는
|
||||
계약이 아니라 대상 밖이라는 뜻으로 흡수, (b) 부분 매핑은 §7-7대로 기각. 매퍼
|
||||
생성기 범위에 컨테이너를 넣을 일도 없다(`D`와 같은 범위).
|
||||
12. **`type <Class>Param`의 배열 파트 — 원소 타입을 파라미터로.** 사용자: *"내가
|
||||
생각한게 원소를 파라미터로 받는거였어. 거기에 Instance 또는 Instance|MapperDescriptor
|
||||
가 오는거지"*. `FrameParam<E>` — `D.Frame`은 `E` = 기존 children 원소 유니언,
|
||||
`D.Mapper.Frame`은 `E` = 그것 `| MapperDescriptor`(§2). 실제 Luau에서 도는지는
|
||||
`luau-analyze` 스파이크로(§9).
|
||||
|
||||
## 8. 검토 후 안 만들기로 한 것
|
||||
|
||||
- `Claim<<"Frame">>` 타입 인자 / 루트를 맨 테이블로(§7-1).
|
||||
|
|
@ -264,18 +297,27 @@ derive 를 걸어야해. 이건 derive 에선 구현하지 않고, 그 위의
|
|||
- `Mount(root, parent)`류 표면(`H-146`, §5).
|
||||
- 이미 있는 Instance에 나중에 props를 재바인드(`archive/existing-instance-bind-rejected.md`,
|
||||
헤더).
|
||||
- **[2026-08-28 후속]** 이중 claim용 별도 레지스트리·`elementOwner` 기록(§7-10) /
|
||||
`nativeAdopt`라는 이름, `Claim` 본체를 프로바이더에 두기, (0) 셋업을 quad-base로
|
||||
옮기기(§7-9) / `PlayerGui`·`CoreGui`류 공동 소유 컨테이너 claim, 매퍼 생성 범위에
|
||||
컨테이너 추가(§7-11) / 필드 파트만 빼고 배열은 각자 두는 타입 둘(§7-12).
|
||||
|
||||
## 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 착수 때).
|
||||
- `D.Mapper.<Class>` 생성기 산출 + `type <Class>Param<E>`(사용자 확정, §7-12 — `E`
|
||||
파라미터가 실제 Luau에서 `D.Frame`/`D.Mapper.Frame` 둘을 통과시키는지 `luau-analyze`
|
||||
스파이크, `luau-test/STATUS.md`에 등록) + 루트 센티널(사용자 확정 — 놓는 자리
|
||||
`D.Mapper.Root`는 에이전트 제안) + 디스크립터 브랜드(`MapperDescriptor`는 가칭 —
|
||||
`Brand` 인스턴스 브랜드로 만든다는 것은 `base/brand-plan.md`의 일반 규칙이지 새 결정
|
||||
아님).
|
||||
- `Claim(inst, desc) -> inst` — DFS 해석 → 해석한 inst마다 `nativeClaim` → bottom-up
|
||||
`drive`, 소진 플래그(§2), 이중 claim은 `nativeClaim` 앞의 `InstData` 검사(§7-10).
|
||||
**구현 시 정할 것**: 사용자 테이블 in-place 교체 vs 새 테이블, Modifier 안의
|
||||
디스크립터 처리, 패키지 안 정의 파일 위치(`quad-base/src/Claim.luau` 가칭 —
|
||||
`base/architecture.md` 소스 트리에 반영은 M5 착수 때).
|
||||
- 프로바이더 op **`nativeClaim(inst)`**(§7-9) — `lifecycle-pattern.md` (0)의 코드가 본체,
|
||||
`New` ②단계가 같은 op를 부르도록 `base/bind-system-plan.md` 의사코드 주석 갱신됨.
|
||||
`base/architecture.md` 주입 op 목록에 추가됨(조합 폴백 예외 — 셋업이라 조합 불가).
|
||||
- 프로바이더 op `nativeFindChild(inst, key)`(이름 가칭) — `base/architecture.md` 주입 op
|
||||
목록에 추가됨(quad-roblox는 `inst:FindFirstChild(key)`). "`native*` 조합 폴백의
|
||||
예외 — 조회라 조합으로 만들 수 없어 `isInst`처럼 미주입이면 명확한 error"는
|
||||
|
|
@ -287,35 +329,10 @@ derive 를 걸어야해. 이건 derive 에선 구현하지 않고, 그 위의
|
|||
여러 스크립트의 PlayerGui는 각자 `ScreenGui` + 중간 모듈 패턴, claim된 inst에선
|
||||
`PreRef`/`OnCreated`가 "이미 있는 것 위에서" 뜬다는 것(§4).
|
||||
|
||||
## 10. 사용자 판단 필요 — M5 착수 전 (2026-08-28 `/code-review high`가 낸 공백)
|
||||
## 10. [해소됨, 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번 — *"추론만으로 … 확정하지 말 것"*).
|
||||
**전부 사용자가 같은 날 답해 §7의 9~12번으로 들어갔다** — A(gcconn/gchold 셋업 자리)
|
||||
→ §7-9 `nativeClaim` / B(이중 claim 레지스트리) → §7-10 문항 자체가 틀림, 셋업 유무로
|
||||
판정 / C(`PlayerGui` own-all) → §7-11 claim 대상이 아님 / D(`<Class>Param` 배열 파트)
|
||||
→ §7-12 원소 타입 파라미터. 당시 갈래·권고 원문은 `session/2026-08-28-02-claim-promotion.md`
|
||||
끝 절. 이 절 번호를 가리키던 바깥 문서들은 그 절로 가리키게 고쳤다.
|
||||
|
|
|
|||
|
|
@ -249,8 +249,12 @@ userdata 포인터**다. Lua 쪽에서 아무도 참조를 안 들고 있으면
|
|||
**자기가 만든 Instance마다 생성 즉시 Lua 쪽 강참조를 하나 심어** 바인딩이
|
||||
살아있는 동안 userdata 동일성을 고정한다:
|
||||
|
||||
**[2026-08-28 `Claim` §7-9]** 아래 코드는 주입 op **`nativeClaim(inst)`**의 본체다 —
|
||||
`New`의 ②단계와 `Claim`(이미 있는 트리를 소유할 때, `base/claim-plan.md`)이 같은
|
||||
op를 부른다. quad가 소유하는 Instance마다 정확히 한 번.
|
||||
|
||||
```lua
|
||||
-- quad-roblox: Instance를 만든 직후 무조건 실행(핸들러/바인딩 유무와 무관)
|
||||
-- quad-roblox: nativeClaim(inst) — Instance를 만든 직후 / claim 직후 무조건 실행
|
||||
local nop = false or function(...) end -- local이라 상수 접힘/인라인 안 됨
|
||||
|
||||
local gchold = {} -- 이 inst에 매달린 값들의 강참조 홀더
|
||||
|
|
|
|||
|
|
@ -117,7 +117,8 @@ base는 여전히 `T`가 뭔지 모른다 — **아는 건 백엔드고 base는
|
|||
- `isInst`는 이 조합 폴백의 예외다(위 문단) — 판정이라 조합으로 만들 수
|
||||
없고, 미주입이면 명확한 에러여야 한다. 같은 예외가 **`onDestroying`**(`H-11`,
|
||||
훅)과 **[2026-08-28] `nativeFindChild`**(`Claim`의 조회 op, `base/claim-plan.md` —
|
||||
예외 분류는 에이전트 판단)에도 적용된다. 전체 목록의 소스는
|
||||
예외 분류는 에이전트 판단)·**`nativeClaim`**(gcconn/gchold 셋업, `Claim` §7-9)에도
|
||||
적용된다. 전체 목록의 소스는
|
||||
`base/architecture.md`의 소스 트리(`EngineOps.luau` 줄).
|
||||
- **⚠️ 전제 — 한 Slot의 물리 자식은 부모 안에서 연속 구간을 차지한다.** 범위 op이
|
||||
성립하는 근거가 전부 이것이다(offset이 누적합이고 중첩 Slot도 같은
|
||||
|
|
|
|||
|
|
@ -74,21 +74,6 @@
|
|||
> `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라던가 좀 부정확하거나 느낌이 바로 와닿지
|
||||
|
|
|
|||
|
|
@ -516,8 +516,8 @@ Tween mock 등 동적 동작 포함")와 목적이 다름:
|
|||
- `Claim(inst, D.Mapper…)`은 이름 중복·부재를 UB로 두고 debug 모드에서만 `seen`
|
||||
맵으로 잡는다(사용자 제안). **어디까지 잡을지는 여기서 정한다** — 후보: 이름
|
||||
중복 / 부재 / 클래스 불일치 / 미매핑 부기 대상 자식 / 디스크립터 순서와 물리
|
||||
순서 불일치(web) / 같은 `inst` 이중 claim / 루트 센티널 `D.Mapper.Root`가 자식
|
||||
자리에 온 것. 사용자 판단(2026-08-28): *"디버깅 도구 만들 때 고려해야할 점으로
|
||||
순서 불일치(web) / 루트 센티널 `D.Mapper.Root`가 자식 자리에 온 것(같은 `inst`
|
||||
이중 claim은 debug가 아니라 `nativeClaim` 앞 런타임 error — `base/claim-plan.md` §7-10). 사용자 판단(2026-08-28): *"디버깅 도구 만들 때 고려해야할 점으로
|
||||
옮겨져야해. 부분 부분 디버깅 가능성을 아직 다 논한게 없어서 지금 그림으로 보면
|
||||
작은 그림을 먼저 그리는거라서, 미결상황으로, 위치 이동이 필요함"* — 즉 "debug
|
||||
모드가 무엇을 언제 검사하는가"의 큰 그림(이 문서) 안에서 같이 정할 것.
|
||||
|
|
|
|||
|
|
@ -183,9 +183,10 @@ v1 폐기 API/버그/구조 결함 전부 v2 설계를 정당화하는 내부
|
|||
중복·부재는 UB(debug에서 error); (2) **루트의 `.Parent =`는 밖에서, 그 아래는
|
||||
절대 직접 하지 말 것** — props에 `Parent`를 넣으면 일반 매치 실패 문구가 나오는
|
||||
이유도 여기서 설명; (3) 여러 스크립트가 한 `PlayerGui`를 쓸 땐 각자 `ScreenGui`를
|
||||
만들어 `.Parent = PlayerGui`, PlayerGui 직하 Slot을 공유해야 하면 `Claim`을 한
|
||||
번 하고 Slot을 반환하는 중간 모듈 패턴(**단 PlayerGui 자체를 claim하는 것은
|
||||
`base/claim-plan.md` §10-C 미결** — 확정 전엔 쓰지 말 것)
|
||||
만들어 `.Parent = PlayerGui`, 한 Slot을 여러 스크립트가 공유해야 하면 `ScreenGui`
|
||||
하나를 만들거나 claim하고 Slot을 반환하는 중간 모듈 패턴; **`PlayerGui`·`CoreGui`
|
||||
같은 공동 소유 컨테이너는 claim 대상이 아니다**(`base/claim-plan.md` §7-11) —
|
||||
"왜 PlayerGui를 claim하면 안 되는가"를 설명할 것
|
||||
|
||||
(13번이었던 "Fusion/Vide 경험자용 비교 섹션"은 2026-08-06 재분류로 아래 6번
|
||||
`quadnomicon`으로 이동)
|
||||
|
|
|
|||
|
|
@ -2010,4 +2010,8 @@ Q4(`EffectHandle` 네 진입점 의사코드 — Observer 것 재사용, `Unsubs
|
|||
레지스트리 / `PlayerGui` own-all vs `ResetOnSpawn` / `<Class>Param` 배열 파트). 리뷰가
|
||||
잡은 실질 정정: `Claim<T>` 추론은 `Clone()`이 `Instance`를 돌려줘 성립 안 함 →
|
||||
반환은 inst 타입 그대로 / `Processed` 관용구는 플래그 절반만 / claim된 inst에선
|
||||
`PreRef`/`OnCreated` 불변식이 약해짐.
|
||||
`PreRef`/`OnCreated` 불변식이 약해짐. **같은 날 문항 넷도 답이 와 닫힘**(§7-9~12):
|
||||
주입 op **`nativeClaim(inst)`**에 gcconn/gchold (0) 경로 전부(`New` ②도 호출) /
|
||||
이중 claim은 셋업 유무로 error(*"claim 은 slot 이랑 무관"* — 리뷰 전제 기각) /
|
||||
**`PlayerGui`는 공동 소유 객체라 claim 대상 아님**, 루트는 `ScreenGui`·`SurfaceGui` /
|
||||
`FrameParam<E>` 원소 타입 파라미터.
|
||||
|
|
|
|||
|
|
@ -133,3 +133,18 @@ M2 배너·M5 두 체크박스 / `question.md` 최우선 절 비움 + `archive/q
|
|||
own-all vs `ResetOnSpawn` / 공유 `Param` 배열 파트)은 `base/claim-plan.md` §10과
|
||||
`question.md`에 문항으로 — 사용자 결정 대기. 리뷰가 제안한 이름(`nativeAdopt`,
|
||||
`FrameParam<C>`)은 전부 가칭.
|
||||
|
||||
## 사용자 원문 (3차 메시지) — `/code-review` 문항 넷의 답
|
||||
|
||||
> A. 이건 nativeClaim 을 만들고 gchold/gcconn 경로를 여기에 전부 두면 되지 않을까
|
||||
> 생각중. B. 정확히는, claim 은 slot 이랑 무관하지 않아? 이중 claim 자체가 무슨
|
||||
> 상황이야. C. 는 애초에 PlayerGui 자체를 Own 한다는게 좀 잘못되었어. 공동 소유
|
||||
> 가능 객체인데 그러는거지. ScreenGui/SurfaceGui 등으로 생각해야지. D. 내가 생각한게
|
||||
> 원소를 파라미터로 받는거였어. 거기에 Instance 또는 Instance|MapperDescriptor 가
|
||||
> 오는거지
|
||||
|
||||
읽기와 반영은 `base/claim-plan.md` §7-9~12. 에이전트 읽기 하나 — A의 "전부"를 `New`
|
||||
②단계도 `nativeClaim`을 부르는 것으로 옮겼다(`bind-system-plan.md` 의사코드 주석,
|
||||
`lifecycle-pattern.md` (0) 머리). B는 리뷰 문항의 전제(`elementOwner` 충돌)가 틀렸다는
|
||||
답이라 레지스트리 없이 셋업 유무로 판정. C로 §6의 `Claim(PlayerGui, …)` 예시가
|
||||
폐기되고 중간 모듈 패턴의 루트가 `ScreenGui`로 바뀌었다.
|
||||
|
|
|
|||
|
|
@ -63,10 +63,10 @@
|
|||
`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 착수.**
|
||||
`question.md` 최우선 절은 비어 있다. 승격 뒤 `/code-review high`가 낸 **M5 착수 전
|
||||
문항 넷**도 같은 날 확정(`base/claim-plan.md` §7-9~12 — `nativeClaim` 주입 op에
|
||||
gcconn/gchold 경로 전부 / 이중 claim은 셋업 유무로 / `PlayerGui`는 claim 대상 아님 /
|
||||
`<Class>Param<E>`). `question.md`엔 `Claim` 항목이 없다. 남은 액션: M2 착수.**
|
||||
아래는 돌리기 전(2026-08-26) 서술:
|
||||
지시서는 `qa-request/pre-implementation-handtrace-round9-brief.md`. 스코프는
|
||||
**커밋 `9dd8213` 하나의 델타**다 — 8라운드 결정 반영과 그 뒤
|
||||
|
|
|
|||
17
ROADMAP.md
17
ROADMAP.md
|
|
@ -34,7 +34,7 @@ quad-v2 구현 단계 실행 계획. 설계 근거/아키텍처 자체는 여기
|
|||
> 이미 있는 트리는 **quad가 `Claim`으로 소유**(`H-148`, `base/claim-plan.md`,
|
||||
> **M5 스코프** — `H-161`; 같은 날 갈래까지 전량 확정 — 그 과정에서 **루트의
|
||||
> `.Parent =`는 밖에서 허용으로 복원**, 요지는 M5 `Claim` 체크박스; `/code-review`가
|
||||
> 낸 M5 착수 전 문항 넷은 그 문서 §10). 그 밖에 `_epochs`는 emit 때만 갱신
|
||||
> 낸 M5 착수 전 문항 넷도 같은 날 확정 — 그 문서 §7-9~12). 그 밖에 `_epochs`는 emit 때만 갱신
|
||||
> (`Refresh` 캐치업 폐기, `H-151`) / `Effect._blocker` 제거(`H-150`) / Observer
|
||||
> 진입점 인라인(`H-149`) / `GateNode` 브랜드 등록(`H-152`) / Store 예약 이름
|
||||
> 런타임 가드(`H-153`) / `InstanceChildHandler` dedup(`H-154`).
|
||||
|
|
@ -951,9 +951,10 @@ 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`.
|
||||
> **[2026-08-28]** 셋째·넷째로 **`nativeFindChild(inst, key)`**(`Claim`의 조회 op,
|
||||
> 예외 분류는 에이전트 판단)와 **`nativeClaim(inst)`**(gcconn/gchold 셋업의 유일한
|
||||
> 자리 — `New` ②단계도 이걸 부른다, 사용자 확정) — 아래 `Claim` 체크박스·
|
||||
> `base/claim-plan.md` §7-9. 전체 목록의 소스는 `base/architecture.md`.
|
||||
|
||||
> **⚠️ 구현 관례**: `quad-roblox`의 공개 타입은 지금부터 단일 파일
|
||||
> (`src/init.luau` 또는 `types.luau`)에 몰아둘 것 — 나중에 필요해지면
|
||||
|
|
@ -976,7 +977,7 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
문단. 빠뜨리면
|
||||
`Frame { Frame{}, Slot() }`이 첫 마운트에서 죽는다 —
|
||||
`base/dispatch-core-plan.md`의 `H-39` 블록(그 다섯째 항목)이 소스.
|
||||
- [ ] **[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>`가 공유하도록 찍는다.
|
||||
- [ ] **[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`으로만"으로 폐기됐다가 같은 날 복원). **[같은 날 후속]** `/code-review`가 낸 문항 넷도 확정(`base/claim-plan.md` §7-9~12): gcconn/gchold 셋업은 주입 op **`nativeClaim(inst)`**에만(`New` ②단계도 호출) / 이중 claim은 그 셋업 유무로 error(레지스트리 없음) / **`PlayerGui`류 공동 소유 컨테이너는 claim 대상 아님**(루트는 `ScreenGui`·`SurfaceGui`) / `D/init.luau` 생성기는 `type <Class>Param<E>`(원소 타입 파라미터)를 `D.<Class>`·`D.Mapper.<Class>`가 공유하도록 찍는다 — `luau-analyze` 스파이크 필요.
|
||||
- [ ] **Instance 생성 시점의 gcconn/gchold 셋업**(2026-08-14 다섯 번째 세션
|
||||
확정, 옛 "`bindLifetime` 첫 호출에서 lazy 생성"에서 전환 — `base/
|
||||
lifecycle-pattern.md`의 "(0) gcconn/gchold는 Instance 생성 시점에
|
||||
|
|
@ -989,7 +990,11 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
(`elementOwner`/`nameClaims`/Tag 참조카운트 등)가 성립함. 대가는
|
||||
"quad가 만든 Instance는 참조를 놓는 것만으로 회수되지 않고 반드시
|
||||
`Destroy`가 필요" — 바인딩이 하나라도 걸리면 어차피 같은 순환이
|
||||
생기므로 실질적 신규 제약은 아님
|
||||
생기므로 실질적 신규 제약은 아님. **[2026-08-28 `Claim` §7-9]** 이
|
||||
셋업은 `New` 안 인라인이 아니라 **주입 op `nativeClaim(inst)`의 본체**로
|
||||
구현한다 — `New` ②단계와 `Claim`(이미 있는 트리, 위 체크박스)이 같은
|
||||
op를 부르고, 이미 셋업된 inst면 error(이중 claim 판정). 사용자 확정
|
||||
*"gchold/gcconn 경로를 여기에 전부"*.
|
||||
- [ ] 실제 Roblox에서 첫 `Frame{...}` 렌더 확인 — **Studio 작업이라
|
||||
`HUMAN_TODO.md` 1번(계정 분리) 먼저 되어야 진행 가능, `SAFETY.md` 준수**
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue