decide(dispatch,modifier,store): pre-implementation-audit 우선순위1 마지막 넷 해소, 누락 스파이크 보강
사용자가 우선순위 동률/매치실패(1-3), provider 미주입(1-4), store.key type function 타이핑(1-10), Modifier __index+table.clone 트릭(1-11)에 대해 구체적 결정을 제시 - 전부 base/ 문서에 반영해 우선순위1 11개 전원 해소. 핸드오버 점검 중 1-10/1-11이 설계 레벨로만 확인됐고 실제 Luau 스파이크 파일이 없었던 갭을 발견해 16/17 신규 추가, ROADMAP.md M2/M3/M7 체크리스트 누락분도 보강. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VVG74qV2nQVykhvMRQW2UC
This commit is contained in:
parent
2df591851f
commit
4c5ee1b66a
11 changed files with 493 additions and 37 deletions
|
|
@ -63,6 +63,41 @@ v1의 `ProcessQuadProperty`(`.claude/initreq/quad/src/class.lua:134-214`)는
|
|||
src/schema/union.luau:48-68`) — 에러 메시지는 즉시 문자열로 만들지 말고 매치
|
||||
실패 시에만 클로저 호출.
|
||||
|
||||
**우선순위 동률/매치 실패 처리 — 확정(2026-08-12 열일곱 번째 세션,
|
||||
`pre-implementation-audit.md` 1-3/1-4 해소).**
|
||||
|
||||
- **동률(같은 `priority` 값)에 대한 tiebreak 규칙은 강제하지 않는다.**
|
||||
"등록 순서가 이긴다" 같은 규칙을 강제하면 `NoneHandler`/`StoreBind`처럼
|
||||
이미 서로 `isHandlable`이 안 겹치는 내장 핸들러에까지 전부 그 규칙을
|
||||
지켜가며 순서를 신경 써야 하고, 나중에 서드파티 핸들러가 늘어나면 더
|
||||
골치아파짐(사용자 판단). 대신 **목적별로 이름 붙은 우선순위 상수**
|
||||
(`HANDLER_PRIORITY_HIGH`/`HANDLER_PRIORITY_NORMAL`/`HANDLER_PRIORITY_LOW`
|
||||
등, 여전히 열린 숫자 공간 위의 편의 상수라 `HANDLER_PRIORITY_HIGH + 1`처럼
|
||||
세밀 조정도 가능)를 제공해 애초에 동률이 잘 안 나오게 유도 — "우선순위
|
||||
밴드 + 오프셋"은 여러 업계에서 이미 흔한 패턴. 실제로 동률이 나면 그건
|
||||
대개 핸들러 설계 실수라, 강제 규칙보다 아래 디버그 가시성으로 대응하는
|
||||
쪽이 맞음.
|
||||
- **매치 실패(`isHandlable`을 만족하는 핸들러가 하나도 없음)는 조용한
|
||||
무시 없이 즉시 `error`.** 에러 메시지엔 값의 `Brand`(있으면)와
|
||||
`typeof(v)`를 함께 출력하고, "quad-roblox 등 필요한 provider가
|
||||
초기화됐는지 확인하라"는 안내만 덧붙임 — 그 이상의 특수 분기는 두지
|
||||
않음(다른 라이브러리에서도 흔한 "매치 실패=에러" 패턴 그대로).
|
||||
**이걸로 `module-lifecycle-plan.md`의 "provider가 아직 주입 안 된
|
||||
상태에서 dispatch가 호출되면?" 케이스(`pre-implementation-audit.md`
|
||||
1-4)도 별도 분기 없이 자동으로 해소됨** — provider 미주입 상태는
|
||||
결국 그 클래스를 다루는 핸들러가 레지스트리에 하나도 없는 상태이므로
|
||||
"매치 실패"와 정확히 같은 경로로 수렴함. 오타 키/미지원 조합/provider
|
||||
미주입을 서로 다른 에러 종류로 구분할 필요가 없음.
|
||||
- **디버그 모드 — 핸들러 등록/정렬 시점에 동률 감지 시 print 경고 +
|
||||
전체 핸들러 목록 조회 함수.** 우선순위는 핸들러 등록 시점에 정적으로
|
||||
sort되므로 동률 감지 자체는 그 시점에 공짜로 가능 — `priority`가 같은
|
||||
두 핸들러가 등록되면 콘솔에 경고를 찍고, `Dispatch.listHandlers()`류
|
||||
함수로 현재 등록된 전체 핸들러(이름/priority)를 덤프할 수 있게 함.
|
||||
구현 비용이 거의 없고 실제 개발 중 디버깅에 바로 도움되는 항목이라
|
||||
M2(Dispatch 엔진) 착수 시 기본 기능으로 같이 넣음 — 런타임 플러그인인
|
||||
`quad-debug`(후순위, `research/debug-tooling-plan.md`)와는 다른 층위의,
|
||||
라이브러리 자체에 내장된 개발자 편의 기능.
|
||||
|
||||
## 확정된 디스패치 모델: `process(inst, k, v)` / `retract(inst, k, v)`
|
||||
|
||||
**사용자가 직접 준 구체적인 모델 — 이 문서의 이전 초안보다 우선함.** 아래가
|
||||
|
|
@ -2258,6 +2293,45 @@ Modifier처럼 플래튼하지 않는가"는 설계 근거를 알고 싶은 사
|
|||
가드를 넣어 재설계하는 대신, State 자체가 파이핑 결합체이고
|
||||
`state(state)`로 분기하는 위 모델로 완전히 대체됨.
|
||||
|
||||
**`store.key` 레코드 필드 타이핑 — Luau 타입함수로 해결 확인
|
||||
(2026-08-12 열일곱 번째 세션, `pre-implementation-audit.md` 1-10 해소).**
|
||||
|
||||
위 "타입 추론 문제" 절이 "`store.key`를 평범한 레코드 필드 타이핑으로 자동
|
||||
해결"이라 서술했지만, `Store<T>`가 입력 `T`(예: `{ty: string}`)를 받아
|
||||
`{ty: Source<string>}`류 결과 타입을 실제로 어떻게 합성하는지는 미검증으로
|
||||
남아있었음. **Luau의 `type function`**(컴파일타임에 타입을 인자로 받아 새
|
||||
타입을 조립하는 기능, https://luau.org/types/type-functions/ ,
|
||||
https://luau.org/types-library/ — tbox에서도 이미 쓰이는 검증된 패턴)으로
|
||||
정확히 풀림:
|
||||
|
||||
```luau
|
||||
type function WrapStore(ty: type): type
|
||||
-- Source<T> 형태를 그대로 조립(:Get/:Set/:Compute/:With 등)
|
||||
local result = types.newtable()
|
||||
result:setproperty(types.singleton("Get"), types.newfunction(...))
|
||||
return result
|
||||
end
|
||||
|
||||
type function ProcessStoreType(ty: type): type
|
||||
local props = ty:properties() :: { [type]: { read: type?, write: type? } }
|
||||
local result = types.newtable()
|
||||
for i, v in props do
|
||||
-- i는 프로퍼티 이름을 담은 singleton 타입, i:value()로 실제 문자열
|
||||
result:setproperty(i, WrapStore(v))
|
||||
end
|
||||
return result
|
||||
end
|
||||
```
|
||||
|
||||
`ProcessStoreType<{ty: string}>` → `{ty: Source<string>}`가 나옴 — 결과는
|
||||
선언 시점에 이름 붙은 `Source<string>` 그 자체가 아니라 구조를 그대로 풀어낸
|
||||
(flatten) 익명 타입이지만, **Luau는 이름이 아니라 "만족하는가"로 구조적
|
||||
일치를 검사**하므로 문제없이 `Source<string>` 자리에 대입 가능 — 오히려 이
|
||||
방식과 정확히 맞는 조합. 이걸로 `store.key`가 실제로 타입 명시 가능함이
|
||||
확인돼 M0/M3 어느 시점에 검증해도 기술적으로 막힐 위험은 없음 —
|
||||
`ROADMAP.md`의 M0/M3 배치를 강제로 바꿀 필요는 없어짐, 검증 난이도
|
||||
문제였던 것만 해소.
|
||||
|
||||
**PA님 코드와의 교차검증(2026-08-04 4차 라운드) — 둘 다 기존 확정 유지**
|
||||
|
||||
`.claude/initreq/artworks/EventDrivenProgramming/`(Connection/Event/
|
||||
|
|
|
|||
|
|
@ -45,6 +45,21 @@ cascade 문제가 그대로 오는데, 이건 이미 확정된 "Store 바인드
|
|||
참고 — 여기서 다루는 건 컴포지션 타임의 modifier 값 자체 flatten이라 층위가
|
||||
다름.
|
||||
|
||||
**참고 — Property(일반 프로퍼티)에 Attribute식 "이름 소유권 레지스트리"를
|
||||
적용하는 안은 검토 후 기각(2026-08-12 열일곱 번째 세션).** `Attribute`
|
||||
(`base/attribute-plan.md`)의 그룹 위임은 이름별 전용 키 객체가 "지금 이
|
||||
이름을 누가 쓰고 있는가"를 스스로 추적해 충돌을 `error`로 잡아주는데, 이
|
||||
패턴을 일반 Instance 프로퍼티(`BackgroundColor3` 등)에도 그대로 적용할 수
|
||||
있을지 검토했으나 기각됨 — **Attribute 이름은 호출자가 자유롭게 짓는
|
||||
네임스페이스라 자기 전용 키 객체를 새로 만들 수 있지만, Instance 프로퍼티
|
||||
이름은 엔진이 이미 정해둔 유한 집합이라 호출자가 자기만의 전용 키를 새로
|
||||
못 만든다.** 그래서 "이 프로퍼티를 지금 누가 소유하고 있는가"라는 질문
|
||||
자체가 원천적으로 성립하지 않음 — 여러 modifier/컴포넌트가 같은 프로퍼티를
|
||||
건드리는 게 오히려 흔한 정상 시나리오(테마 오버라이드 등)이기도 함. 이게
|
||||
Property가 소유권 추적 대신 **덮어쓰기 우선순위(위 "2. Merge 우선순위"
|
||||
절)**로 처리되는 이유 — 새로 결정한 게 아니라 이미 확정된 설계가 왜
|
||||
그 모양인지를 명문화한 것.
|
||||
|
||||
### 2. Merge 우선순위: 배열 순서와 인라인은 독립된 두 규칙
|
||||
|
||||
`Frame { modifier1, modifier2, Name = ... }` 평탄화 시:
|
||||
|
|
@ -167,6 +182,19 @@ Compute를 합치고 싶다"는 동기였는데, 이미 두 가지로 다 커버
|
|||
메타테이블까지 그대로 복사해주는 Luau 동작 덕분에 클론해도 체이닝이 안
|
||||
끊김. flatten도 그 테이블 필드를 직접 읽으면 됨.
|
||||
|
||||
**`table.clone`의 정확한 동작 — 확인됨(2026-08-12 열일곱 번째 세션,
|
||||
`pre-implementation-audit.md` 1-11 해소).** 새 빈 테이블을 만들고 원본의
|
||||
키를 네이티브 슬롯 단위로 복사(얕은 복사, 값 자체는 안 파고듦)한 뒤,
|
||||
원본의 `getmetatable` 결과를 그대로 그 새 테이블에 `setmetatable` — **이
|
||||
메타테이블은 복사되는 게 아니라 같은 참조를 공유**함. 그래서 위 제네릭
|
||||
`__index` 함수(메타테이블에 딱 하나 있는 그 함수 자체)는 원본과 clone
|
||||
사이에서 물리적으로 동일 객체이고, `mod:FontSize(14)` → `__index`가 리턴한
|
||||
클로저가 `table.clone(self)`로 새 테이블을 또 만들 때도 그 새 테이블 역시
|
||||
같은 메타테이블을 공유하며 체이닝이 끊기지 않음 — M7 전체 설계("클래스별
|
||||
런타임 코드 없이 base에 제네릭 `__index` 하나면 충분")가 의존하던 두 Luau
|
||||
동작(제네릭 `__index`가 임의 key를 잡아 클로저를 즉석 생성하는 것,
|
||||
`table.clone`이 메타테이블 참조를 보존하는 것) 모두 공식 동작대로 확인됨.
|
||||
|
||||
**런타임은 클래스별 코드 없이 base에 딱 하나만 있으면 됨(2026-08-06 후속
|
||||
세션, 핵심 통찰).** `mod:FontSize(14)`는 `mod.FontSize(mod, 14)`로 풀리는
|
||||
문법 설탕이고, `mod.FontSize`는 `FontSize`가 리터럴 키로 안 박혀있으니
|
||||
|
|
|
|||
|
|
@ -119,6 +119,12 @@ init하려 하면 오류, 없는데 뭔가 생성해서 bind하려 해도 오류
|
|||
처리한다"는 의미가 빠져 있음 — `Handler`가 계약 4종
|
||||
(`isHandlable`/`priority`/`process`/`retract`) 전체를 가장 정확히
|
||||
담는다는 결론.
|
||||
- **[해소됨, 2026-08-12 열일곱 번째 세션]** provider(팩토리)가 아직 한
|
||||
번도 실행 안 된 상태에서 dispatch가 호출되면 어떻게 되는지
|
||||
(`pre-implementation-audit.md` 1-4) — 별도 케이스로 처리하지 않음.
|
||||
provider 미주입 상태는 결국 그 클래스의 핸들러가 레지스트리에 하나도
|
||||
없는 상태이므로, `base/bind-system-plan.md` "우선순위 동률/매치 실패
|
||||
처리" 절의 일반 "매치 실패 시 즉시 error" 규칙 하나로 자연히 커버됨.
|
||||
- base 유틸(per-instance 상태 저장소, 생명 바인드 유틸)이 인터페이스만 두고
|
||||
실제 구현은 백엔드 팩토리(`RobloxFactory(BaseModule)`류)가 뮤테이션으로
|
||||
주입한다는 패턴이 확정됨 — 상세는 `base/bind-system-plan.md`의 "base
|
||||
|
|
|
|||
67
.claude/luau-test/16-type-store-key-typefunction.luau
Normal file
67
.claude/luau-test/16-type-store-key-typefunction.luau
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
--!strict
|
||||
--[[
|
||||
검증 대상: `Store<T>`가 레코드 타입 T의 각 필드를 Source로 감싼 타입을
|
||||
Luau `type function`으로 실제로 합성할 수 있는지 — 실제 실행이 아니라
|
||||
타입 체크(luau-analyze/luau-lsp) 대상.
|
||||
|
||||
배경: .claude/base/bind-system-plan.md "`store.key` 레코드 필드 타이핑"
|
||||
절(2026-08-12 열일곱 번째 세션) — 사용자가 제시한 WrapStore/
|
||||
ProcessStoreType 스케치를 그대로 옮김. `research/pre-implementation-audit.md`
|
||||
1-10 해소 근거.
|
||||
|
||||
핵심 우려: (a) `type function` 문법 자체(`types.newtable`/`:setproperty`/
|
||||
`types.singleton`/`ty:properties()`/`types.newfunction`)가 실제 설치된
|
||||
Luau 버전에서 정확히 이 API 모양인지 — type function은 비교적 최근/
|
||||
계속 진화 중인 기능이라 버전에 따라 세부 이름이 다를 수 있음
|
||||
(https://luau.org/types/type-functions/ , https://luau.org/types-library/
|
||||
최신 문서 기준으로 맞춰볼 것 — 아래는 사용자가 손으로 쓴 스케치를 최대한
|
||||
그대로 옮긴 것이라 API 이름이 실제와 다를 수 있음, 그 자체가 이
|
||||
스파이크의 목적). (b) 결과 타입 `ProcessStoreType<{ty: string}>`이
|
||||
실제로 `{ty: Source<string>}`을 "만족"해서(이름이 아니라 구조로)
|
||||
`Source<string>` 필드가 필요한 자리에 그대로 대입 가능한지.
|
||||
|
||||
실행: `luau-analyze 16-type-store-key-typefunction.luau` 또는
|
||||
`luau-lsp analyze` — 문법/API 에러가 나면 정확한 에러 메시지(어느 줄,
|
||||
어떤 API 호출)를 그대로 알려줄 것 — 그게 다음 시도에서 API 이름을
|
||||
고치는 데 바로 쓰임. 이 파일 자체가 한 번에 통과할 걸 기대하기보다,
|
||||
"type function으로 이 방향 자체가 막히는지 아닌지"를 확인하는 게 목적.
|
||||
|
||||
기대 결과: 맨 아래 `checkTy`/`checkCount` 대입이 타입 에러 없이 통과
|
||||
(= `Processed.ty`가 구조적으로 `Source<string>`을 만족).
|
||||
]]
|
||||
|
||||
export type Source<T> = {
|
||||
Get: (self: Source<T>) -> T,
|
||||
Set: (self: Source<T>, value: T) -> (),
|
||||
}
|
||||
|
||||
type function WrapStore(ty: type): type
|
||||
-- Source<T> 형태를 그대로 조립 — 실사용에선 :Set/:Compute/:With 등도
|
||||
-- 같은 방식으로 추가해야 하지만, 이 스파이크는 "필드가 Source로 감싸
|
||||
-- 지는가"라는 구조적 만족 여부만 보면 되므로 :Get만 붙임
|
||||
local result = types.newtable()
|
||||
result:setproperty(types.singleton("Get"), types.newfunction({}, { ty }))
|
||||
return result
|
||||
end
|
||||
|
||||
type function ProcessStoreType(ty: type): type
|
||||
local props = ty:properties() :: { [type]: { read: type?, write: type? } }
|
||||
local result = types.newtable()
|
||||
for i, v in props do
|
||||
-- i는 프로퍼티 이름을 담은 singleton 타입, i:value()로 실제 문자열
|
||||
local fieldType = v.read or v.write
|
||||
if fieldType then
|
||||
result:setproperty(i, WrapStore(fieldType))
|
||||
end
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
type Input = { ty: string, count: number }
|
||||
type Processed = ProcessStoreType<Input>
|
||||
|
||||
local processed: Processed = nil :: any
|
||||
|
||||
-- 검증 포인트: Processed의 각 필드가 실제로 Source<T> 구조를 만족하는지
|
||||
local checkTy: string = processed.ty:Get()
|
||||
local checkCount: number = processed.count:Get()
|
||||
70
.claude/luau-test/17-modifier-index-tableclone-chaining.luau
Normal file
70
.claude/luau-test/17-modifier-index-tableclone-chaining.luau
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
--[[
|
||||
검증 대상: Modifier의 "제네릭 __index + table.clone" 트릭 — 클래스별
|
||||
런타임 코드 없이 하나의 제네릭 __index만으로 임의 필드 setter 체이닝이
|
||||
실제로 동작하는지, 그리고 table.clone이 매 단계 메타테이블을 복사가
|
||||
아니라 참조로 유지해 체이닝이 안 끊기는지.
|
||||
|
||||
배경: .claude/base/modifier-plan.md "런타임은 클래스별 코드 없이 base에
|
||||
딱 하나만 있으면 됨" 절 + "`table.clone`의 정확한 동작 — 확인됨"
|
||||
절(2026-08-12 열일곱 번째 세션) — 사용자가 설명한 메커니즘(원본 키를
|
||||
네이티브 슬롯 단위로 얕은 복사 + 메타테이블은 복사가 아니라
|
||||
getmetatable/setmetatable로 참조 공유)을 실제 코드로 재현.
|
||||
`research/pre-implementation-audit.md` 1-11 해소 근거.
|
||||
|
||||
실행: `luau 17-modifier-index-tableclone-chaining.luau`
|
||||
|
||||
기대 결과: 아래 모든 assert가 통과 — 특히 (B) 메타테이블이 매 clone마다
|
||||
원본과 물리적으로 동일 객체인지(참조 공유 확인), (C) 원본이 각 체이닝
|
||||
단계에서 전혀 mutate 안 됐는지(immutable 보장), (D) 서로 다른 체이닝
|
||||
경로(형제 분기)가 서로 오염 안 시키는지가 핵심.
|
||||
]]
|
||||
|
||||
local function genericIndex(_, key)
|
||||
return function(selfArg, arg)
|
||||
local clone = table.clone(selfArg)
|
||||
if type(arg) == "function" then
|
||||
clone[key] = arg(selfArg[key])
|
||||
else
|
||||
clone[key] = arg
|
||||
end
|
||||
return clone
|
||||
end
|
||||
end
|
||||
|
||||
local ModifierMT = { __index = genericIndex }
|
||||
|
||||
local function Modifier()
|
||||
return setmetatable({}, ModifierMT)
|
||||
end
|
||||
|
||||
-- (A) 필드가 하나도 미리 등록 안 돼 있어도 임의 메소드 이름으로 체이닝되는지
|
||||
local mod0 = Modifier()
|
||||
local mod1 = mod0:FontSize(14)
|
||||
local mod2 = mod1:Round(8)
|
||||
local mod3 = mod2:FontSize(function(old)
|
||||
return old * 1.2
|
||||
end)
|
||||
|
||||
assert(mod1.FontSize == 14, "mod1.FontSize expected 14")
|
||||
assert(mod2.FontSize == 14 and mod2.Round == 8, "mod2 expected FontSize=14, Round=8")
|
||||
assert(mod3.FontSize == 14 * 1.2, "mod3 expected FontSize=16.8 (14*1.2)")
|
||||
|
||||
-- (B) getmetatable이 매 clone마다 원본과 physically 동일 객체인지
|
||||
-- (table.clone이 메타테이블을 복사가 아니라 참조로 공유한다는 주장의 핵심 검증)
|
||||
assert(getmetatable(mod0) == ModifierMT, "mod0 metatable should be ModifierMT")
|
||||
assert(getmetatable(mod1) == getmetatable(mod0), "mod1 metatable should be same reference as mod0's")
|
||||
assert(getmetatable(mod2) == getmetatable(mod0), "mod2 metatable should be same reference as mod0's")
|
||||
assert(getmetatable(mod3) == getmetatable(mod0), "mod3 metatable should be same reference as mod0's")
|
||||
|
||||
-- (C) 원본은 절대 mutate 안 됨(immutable 체이닝 보장)
|
||||
assert(mod0.FontSize == nil, "mod0 must stay untouched (FontSize)")
|
||||
assert(mod0.Round == nil, "mod0 must stay untouched (Round)")
|
||||
assert(mod1.Round == nil, "mod1 must stay untouched (Round) -- clone은 앞 단계까지만 반영")
|
||||
|
||||
-- (D) 서로 다른 체이닝 경로(형제 분기)가 서로 오염 안 시키는지
|
||||
local branchA = mod1:Round(4)
|
||||
local branchB = mod1:Round(100)
|
||||
assert(branchA.Round == 4 and branchB.Round == 100, "sibling branches must not cross-contaminate")
|
||||
assert(mod1.Round == nil, "mod1 itself must stay untouched by either branch")
|
||||
|
||||
print("17-modifier-index-tableclone-chaining: all checks passed")
|
||||
|
|
@ -23,10 +23,15 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해
|
|||
|
||||
| 환경 | 필요한 것 | 해당 파일 |
|
||||
|---|---|---|
|
||||
| **순수 Luau CLI** (`luau`) | [luau-lang/luau 릴리즈](https://github.com/luau-lang/luau/releases)의 `luau` 인터프리터, 또는 `lune` | 01, 02, 03, 04, 05, 06(런타임 부분), 07, 11, 13(런타임 부분) |
|
||||
| **Luau 타입체커** (`luau-analyze` 또는 `luau-lsp`) | 같은 릴리즈에 포함된 `luau-analyze`, 또는 `luau-lsp analyze`/에디터 인라인 진단 | 06(타입 부분), 08, 09, 12, 13(타입 부분), 14, 15 |
|
||||
| **순수 Luau CLI** (`luau`) | [luau-lang/luau 릴리즈](https://github.com/luau-lang/luau/releases)의 `luau` 인터프리터, 또는 `lune` | 01, 02, 03, 04, 05, 06(런타임 부분), 07, 11, 13(런타임 부분), 17 |
|
||||
| **Luau 타입체커** (`luau-analyze` 또는 `luau-lsp`) | 같은 릴리즈에 포함된 `luau-analyze`, 또는 `luau-lsp analyze`/에디터 인라인 진단 | 06(타입 부분), 08, 09, 12, 13(타입 부분), 14, 15, 16 |
|
||||
| **Roblox Studio** | 별도 계정으로 로그인(`HUMAN_TODO.md` 1번, `SAFETY.md` 준수) | 10 |
|
||||
|
||||
**16은 특히 `type function`이라는 비교적 최근/계속 진화 중인 Luau 기능을
|
||||
쓰므로, luau-analyze 버전이 오래되면 아예 문법 자체를 못 알아볼 수 있음**
|
||||
— 그 경우는 실패가 아니라 "이 Luau 버전에서 type function 자체가 아직
|
||||
지원 안 됨"이라는 별개의 신호이니 버전 정보와 함께 알려줄 것.
|
||||
|
||||
**12/13/14는 특히 `luau-lsp`로 확인해달라고 요청받은 것들** — `luau-analyze`도
|
||||
같은 타입 솔버를 쓰므로 원리적으로는 같은 결과가 나와야 하지만, `luau-lsp`가
|
||||
에디터에서 인라인으로 에러 위치/메시지를 보여줘서 "정확히 어느 표현식이
|
||||
|
|
@ -56,6 +61,8 @@ ROADMAP 항목 근거인지, 어떻게 실행하는지, 실행 후 뭘 확인해
|
|||
| `13-type-ref-preref-subtype.luau` | (A, 타입) `PreRef<T>`가 `Ref<T>`를 구조적으로 만족하는지, (B, 런타임) `isRef`/`isPreRef` 합성이 재정정대로 동작하는지(`isRef(preRefInstance)`가 이제 `true`) + Leaf 핸들러가 `isRef(v) and not isPreRef(v)`로 명시적으로 좁혀야 하는 이유 | `bind-system-plan.md`의 `Brand` 절(2026-08-09 열한 번째 세션 재정정) |
|
||||
| `14-type-nilable-default-overload.luau` (타입체크 전용) | `Source(default)`/`Ref(default)`의 `default` 생략이 `T`가 nilable일 때만 안전하다는 캐비엇을, 함수 오버로드(교차 타입)로 실제로 타입 레벨에서 막을 수 있는지 | `bind-system-plan.md` "[보강, 2026-08-09 열한 번째 세션]" 절 |
|
||||
| `15-type-compute-trailing-deps-typepack.luau` (타입체크 전용) | `:Compute(fn, ...)`의 trailing deps를 `fn`에 위치 인자(lazy State 핸들)로도 노출하는 확장, 최종 시그니처 `fn(self, previous?, ...deps)` — 이형(heterogeneous) 다중 deps를 제네릭 타입 팩(`U...`)으로 표현 가능한지, `previous?`가 팩 앞(정정된 순서)에서만 통과하고 팩 뒤(옛 순서)에서는 막히는지 | `bind-system-plan.md` "trailing deps를 fn에 lazy positional 인자로도 노출" 절(2026-08-11 후속 세션, 순서는 같은 날 세 번째 세션에 정정) |
|
||||
| `16-type-store-key-typefunction.luau` (타입체크 전용) | `Store<T>`가 `T`의 각 필드를 `Source`로 감싼 타입을 Luau `type function`(`types.newtable`/`:setproperty`/`ty:properties()`)으로 실제 합성 가능한지, 결과가 구조적으로 `Source<T>` 필드를 만족하는지 | `bind-system-plan.md` "`store.key` 레코드 필드 타이핑" 절(2026-08-12 열일곱 번째 세션), `pre-implementation-audit.md` 1-10 |
|
||||
| `17-modifier-index-tableclone-chaining.luau` | Modifier의 제네릭 `__index`+`table.clone` 체이닝 — 임의 필드 이름에 대해 즉석 setter가 만들어지는지, `table.clone`이 메타테이블을 참조로 공유해 여러 단계 clone에서도 체이닝이 안 끊기는지, 원본이 mutate 안 되는지, 형제 분기끼리 오염 안 되는지 | `modifier-plan.md` "런타임은 클래스별 코드 없이 base에 딱 하나만 있으면 됨" 절 + "`table.clone`의 정확한 동작 — 확인됨" 절(2026-08-12 열일곱 번째 세션), `pre-implementation-audit.md` 1-11 |
|
||||
|
||||
## 갱신 이력
|
||||
|
||||
|
|
@ -101,6 +108,13 @@ deps의 제네릭 타입 팩 표현 가능 여부와 `previous?`를 팩 뒤에
|
|||
검증하는 (D) 양성 대조군을 신규 추가 — 이제 진짜 불확실성은 (B)
|
||||
이형 다중 deps의 제네릭 팩 표현 가능 여부 하나뿐.
|
||||
|
||||
**6차 (2026-08-12, 열일곱 번째 세션, `16`/`17` 신규)**: `pre-implementation-audit.md`
|
||||
우선순위1의 마지막 두 항목(1-10 `store.key` 타이핑, 1-11 Modifier
|
||||
`__index`+`table.clone` 트릭)이 이 세션에 설계 레벨로는 해소됐지만, 실제
|
||||
Luau로 부딪혀본 적은 없다는 걸 핸드오버 점검 중 발견 — `16`(type function
|
||||
스케치, 타입체크 전용)/`17`(제네릭 __index 체이닝, 런타임)로 신규 추가.
|
||||
둘 다 이전까지 이 폴더 어디에도 커버 대상이 없던 완전히 새 항목.
|
||||
|
||||
## 결과 확인 후 할 일
|
||||
|
||||
각 파일 결과를 알려주면, 실제로 걸리는 부분이 있는지 보고 필요하면
|
||||
|
|
@ -130,3 +144,12 @@ deps의 제네릭 타입 팩 표현 가능 여부와 `previous?`를 팩 뒤에
|
|||
- `13`은 A(타입)/B(런타임) 둘 다 확인해줄 것 — B의 assert가 실패하면
|
||||
`Dispatch/Leaf.luau` 설계(`isRef(v) and not isPreRef(v)`) 자체가
|
||||
잘못 짜인 것이니 우선순위 높게 알려줄 것.
|
||||
- `16`은 **어느 쪽으로 나와도 유용** — 통과하면 `store.key` 타이핑을 이
|
||||
type function 방식으로 그대로 채택, API 이름이 틀려서 막히면(`type
|
||||
function`이 비교적 새 기능이라 가능성 높음) 정확한 에러 메시지를 그대로
|
||||
알려줄 것 — 다음 시도의 API 이름을 고치는 데 바로 쓰임. 아예 이 Luau
|
||||
버전이 `type function` 자체를 지원 안 하면 그것도 알려줄 것(다른 대안
|
||||
필요).
|
||||
- `17`은 전부 PASS가 기대값 — 특히 (B) 메타테이블 참조 동일성 assert가
|
||||
실패하면 M7 전체 설계("클래스별 런타임 코드 불필요")의 핵심 전제가
|
||||
무너지는 것이니 최우선으로 알려줄 것.
|
||||
|
|
|
|||
|
|
@ -195,14 +195,16 @@ context-rejected.md`. 아래는 그중 **아직 실제로 열려있는 것만**
|
|||
있던 것을 이번에 `pre-implementation-audit.md` 2-11에도 해소 표시로
|
||||
동기화. **[해소됨, 2026-08-09 세 번째 세션]** Slot CRUD 의미론
|
||||
(`add`/`remove`/`clear`) 미정의(1-7)/`isMounted` 이중 추적 혼용(1-8) —
|
||||
`base/slot-plan.md` 참고. **[2026-08-12 감사 세션 정정] 아직 실제로
|
||||
열려있는 건 하나가 아니라 넷** — `pre-implementation-audit.md` 원문에
|
||||
`[해소됨]` 마커가 없는 항목이 1-3 외에도 셋 더 있음: 우선순위 스캔
|
||||
동률/매치실패 처리(1-3), provider 미주입 상태 dispatch 처리(1-4),
|
||||
`store.key`의 레코드 필드 타이핑을 M0로 앞당길지(1-10), Modifier
|
||||
`__index`+`table.clone` 트릭 검증(1-11) — 이전 요약이 이 셋을 누락한
|
||||
채로 "1-3 하나뿐"이라 잘못 축약해뒀던 것, `pre-implementation-audit.md`
|
||||
본문 및 "다음 액션 제안" 절 참고.
|
||||
`base/slot-plan.md` 참고. **[해소됨, 2026-08-12 열일곱 번째 세션]**
|
||||
나머지 넷도 전부 확정 — 우선순위 스캔 동률/매치실패 처리(1-3, tiebreak
|
||||
강제 없이 `HANDLER_PRIORITY_*` 상수+디버그 print/목록 함수, 매치실패는
|
||||
즉시 error), provider 미주입 상태 dispatch 처리(1-4, 매치실패 규칙에
|
||||
자연 흡수), `store.key`의 레코드 필드 타이핑(1-10, Luau `type function`으로
|
||||
가능함 확인), Modifier `__index`+`table.clone` 트릭 검증(1-11, 메타테이블
|
||||
참조 공유 방식 확인) — 상세는 `pre-implementation-audit.md` 해당 항목,
|
||||
`base/bind-system-plan.md`/`base/modifier-plan.md` 참고. **우선순위1
|
||||
11개 전부 해소됨** — M0 착수 전 남은 건 `.claude/luau-test/` 스파이크
|
||||
실측 결과 확인뿐(아직 사용자가 안 돌려봄).
|
||||
- **[해소됨, 2026-08-08 두 번째 세션]** `Frame { ref }`/`Frame { observer }`처럼
|
||||
children 배열 숫자 슬롯에 직접 놓는 leaf 값을 매칭·바인드하는 Handler
|
||||
(`(i:number, v=Ref/Observer/PreRef)`)의 패키지 배치 — 원래 제안대로
|
||||
|
|
|
|||
|
|
@ -106,7 +106,15 @@ bind-system-plan.md` "Dispatch 체인" 절, `ROADMAP.md` M2/M4. 아래는
|
|||
`retract`를 호출하는 식으로 지금 결정해두는 게 좋아 보임 — M2/M4에서 바로
|
||||
부딪힐 지점.
|
||||
|
||||
### 1-3. 우선순위 스캔의 동률 처리, 매치 실패 시 동작이 정의 안 됨
|
||||
### 1-3. 우선순위 스캔의 동률 처리, 매치 실패 시 동작이 정의 안 됨 — [해소됨, 2026-08-12 열일곱 번째 세션]
|
||||
|
||||
**해소**: 동률은 tiebreak 규칙을 강제하지 않고 대신 목적별 우선순위 상수
|
||||
(`HANDLER_PRIORITY_HIGH` 등, "밴드+오프셋" 패턴)로 애초에 안 나게 유도,
|
||||
매치 실패는 조용한 무시 없이 즉시 `error`(브랜드+`typeof` 출력, provider
|
||||
초기화 확인 안내)로 확정. 핸들러 등록 시점 동률 감지 print 경고와
|
||||
`Dispatch.listHandlers()`류 전체 목록 조회 함수도 M2 기본 기능으로
|
||||
확정 — 상세는 `base/bind-system-plan.md` "우선순위 동률/매치 실패 처리"
|
||||
절. 아래는 원래 발견 당시 기록.
|
||||
|
||||
**위치**: `base/bind-system-plan.md` "핸들러 계약" 절 — "디스패치는 등록된
|
||||
핸들러를 우선순위 순으로 스캔하며 `isHandlable`을 호출, 첫 매치가 처리."
|
||||
|
|
@ -121,7 +129,13 @@ bind-system-plan.md` "Dispatch 체인" 절, `ROADMAP.md` M2/M4. 아래는
|
|||
결정해두면 구현 중 인터럽트를 막을 수 있음. 동률은 "등록 순서가 tiebreak"
|
||||
정도로 명시만 해둬도 충분. M2(Dispatch 엔진) 착수 직전 확인.
|
||||
|
||||
### 1-4. provider(팩토리) 미주입 상태에서 dispatch가 호출되면 어떻게 되는지 세 번째 케이스가 빠짐
|
||||
### 1-4. provider(팩토리) 미주입 상태에서 dispatch가 호출되면 어떻게 되는지 세 번째 케이스가 빠짐 — [해소됨, 2026-08-12 열일곱 번째 세션]
|
||||
|
||||
**해소**: 별도 케이스로 처리하지 않음 — provider 미주입 상태는 결국 그
|
||||
클래스의 핸들러가 레지스트리에 하나도 없는 상태이므로, 위 1-3에서 확정된
|
||||
일반 "매치 실패 시 즉시 error" 규칙 하나로 자연히 커버됨. 상세는
|
||||
`base/module-lifecycle-plan.md` 해당 항목, `base/bind-system-plan.md`
|
||||
"우선순위 동률/매치 실패 처리" 절. 아래는 원래 발견 당시 기록.
|
||||
|
||||
**위치**: `base/module-lifecycle-plan.md` "Bind는 누가, 어떻게 구현하는가"
|
||||
절 — 재호출 가드는 "같은 팩토리=무시, 다른 팩토리=에러" 두 케이스로
|
||||
|
|
@ -274,7 +288,18 @@ base 인터페이스가 그보다 늦은 M8에서 만들어지는 순서 역전.
|
|||
`Connected` 기반)"만 담당하도록 분리. M1 mock에도 이 인터페이스의 트리비얼
|
||||
스텁(항상 true)을 붙여두면 M4/M6 테스트가 자연스러워짐.
|
||||
|
||||
### 1-10. `store.key`의 레코드 필드 타이핑 검증이 M0가 아니라 M3로 밀려 있음
|
||||
### 1-10. `store.key`의 레코드 필드 타이핑 검증이 M0가 아니라 M3로 밀려 있음 — [해소됨, 2026-08-12 열일곱 번째 세션]
|
||||
|
||||
**해소**: 걱정했던 "검증 난이도" 자체가 사라짐 — Luau `type function`
|
||||
(`https://luau.org/types/type-functions/`)으로 `Store<T>`가 `T`의 각
|
||||
필드를 `Source`로 감싼 레코드 타입을 실제로 합성 가능함을 확인(tbox에서도
|
||||
이미 쓰이는 패턴). 결과가 구조적으로 `Source<T>`를 만족하기만 하면 Luau가
|
||||
이름이 아니라 구조로 일치를 검사하므로 문제없음. 기술적으로 막힐 위험이
|
||||
없어졌으니 M0/M3 어느 시점에 검증해도 무방 — `ROADMAP.md` 배치를 억지로
|
||||
안 옮겨도 됨. 상세는 `base/bind-system-plan.md` "`store.key` 레코드 필드
|
||||
타이핑" 절, 실제 문법 실측은
|
||||
`.claude/luau-test/16-type-store-key-typefunction.luau`(신규). 아래는
|
||||
원래 발견 당시 기록.
|
||||
|
||||
**위치**: `ROADMAP.md` M0 vs M3 `"store.key dot-access 타입 추론 확인"`.
|
||||
|
||||
|
|
@ -292,7 +317,20 @@ M1(스캐폴딩)·M2(디스패치 엔진) 투자가 먼저 이뤄진 뒤에야
|
|||
안전하게 추론되는지"도 같이 넣을 것 — 어차피 같은 스파이크 파일에서 몇 줄
|
||||
추가로 검증 가능.
|
||||
|
||||
### 1-11. Modifier의 "제네릭 `__index` + `table.clone` 메타테이블 보존" 트릭이 검증 안 된 채 M7 전체 설계의 전제가 됨
|
||||
### 1-11. Modifier의 "제네릭 `__index` + `table.clone` 메타테이블 보존" 트릭이 검증 안 된 채 M7 전체 설계의 전제가 됨 — [해소됨, 2026-08-12 열일곱 번째 세션]
|
||||
|
||||
**해소**: `table.clone`의 정확한 동작(새 테이블에 원본 키를 네이티브 슬롯
|
||||
단위로 얕은 복사, 메타테이블은 복사가 아니라 `getmetatable`/`setmetatable`로
|
||||
같은 참조를 공유)을 사용자가 직접 확인 — 제네릭 `__index` 함수가 원본과
|
||||
clone 사이에서 물리적으로 동일 객체로 유지되므로 체이닝이 끊기지 않는다는
|
||||
M7의 전제가 Luau 공식 동작대로 성립함. 별도로, 프로퍼티에 Attribute식
|
||||
소유권 레지스트리를 적용하는 대안도 검토했으나 기각(엔진이 정한 유한
|
||||
프로퍼티 이름 집합은 호출자가 전용 키를 못 만들어 소유권 판정이 원천적으로
|
||||
불가능 — Property가 소유권 추적 대신 Modifier의 override 우선순위를 쓰는
|
||||
이유). 상세는 `base/modifier-plan.md` "`table.clone`의 정확한 동작" 절과
|
||||
"Property에 Attribute식 소유권 레지스트리 기각" 절, 실제 실행 확인은
|
||||
`.claude/luau-test/17-modifier-index-tableclone-chaining.luau`(신규).
|
||||
아래는 원래 발견 당시 기록.
|
||||
|
||||
**위치**: `base/modifier-plan.md` "런타임은 클래스별 코드 없이 base에 딱
|
||||
하나만 있으면 됨" 절, `ROADMAP.md` M7.
|
||||
|
|
@ -652,16 +690,22 @@ Handler"라고만 서술해, 사실상 3개의 거의 동일한 형태(리터럴
|
|||
|
||||
## 다음 액션 제안
|
||||
|
||||
- **M0 착수 전**: 1-5(props.Modifier/Ref nil-hole)는 `ROADMAP.md` M0에
|
||||
반영 완료. 1-10(`store.key` 레코드 필드 타이핑)을 M0로 앞당기는 것 검토,
|
||||
1-11(Modifier `__index` 트릭)도 비용이 낮으니 M0 후보로 포함 검토는 계속
|
||||
열려있음.
|
||||
- **M2(Dispatch) 착수 전**: 1-3, 1-4를 한 번에 확정(전부 base dispatch
|
||||
엔진의 에러/상태관리 규칙이라 같이 결정하는 게 효율적). 1-2는 2026-08-08
|
||||
세 번째 세션에 Dispatch 체인+`retractUnder`로 이미 해소됨(위 1-2번 항목
|
||||
참고) — 남은 건 M2 스파이크에서 다단 체인 케이스가 실제로 맞게
|
||||
동작하는지 실측하는 것뿐.
|
||||
**[2026-08-12 열일곱 번째 세션 갱신] 우선순위1 11개 전부 해소됨** — 1-3/
|
||||
1-4/1-10/1-11이 이 세션에서 마저 확정되어(위 각 항목 참고), 더 이상 M0
|
||||
착수 전에 열려있는 우선순위1 항목이 없음. 아래는 남은 실측/검증 항목만
|
||||
정리(전부 "설계는 확정, 실제 Luau/구현으로 부딪혀볼 것만 남음" 상태):
|
||||
|
||||
- **M2(Dispatch) 착수 시**: 1-2/1-3/1-4는 설계 확정 완료 — M2 스파이크에서
|
||||
다단 체인 케이스, 동률 감지 디버그 print, 매치 실패 에러 경로가 실제로
|
||||
맞게 동작하는지 실측.
|
||||
- **M2/M3 착수 전**: 1-6(canExecute 실제 구현) 실측(1-9는 반영 완료, 위
|
||||
항목 참고).
|
||||
- **M0 스파이크**: 1-10(`store.key` type function)과 1-11(`table.clone`
|
||||
메타테이블 공유)은 메커니즘 자체는 이제 확실하지만, 실제 Luau 코드로
|
||||
돌려보는 것 자체는 여전히 스파이크 대상 — `.claude/luau-test/16-type-
|
||||
store-key-typefunction.luau`/`17-modifier-index-tableclone-chaining.luau`로
|
||||
신규 추가됨(2026-08-12 열일곱 번째 세션 핸드오버 점검 중 발견된 갭,
|
||||
이전까진 이 두 항목을 커버하는 스파이크 파일 자체가 없었음). 아직
|
||||
사용자가 안 돌려봄, 결과 미확인.
|
||||
- **나머지**: 해당 마일스톤 착수 시점에 이 문서를 다시 열어 관련 항목만
|
||||
확인하면 됨 — 지금 전부 결정할 필요는 없음.
|
||||
|
|
|
|||
111
.claude/session/2026-08-12-17-priority1-audit-resolved.md
Normal file
111
.claude/session/2026-08-12-17-priority1-audit-resolved.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# 2026-08-12 열일곱 번째 세션 — `pre-implementation-audit.md` 우선순위1 마지막 넷 전부 해소
|
||||
|
||||
**배경**: 이전 세션(열여섯 번째)까지 `research/pre-implementation-audit.md`의
|
||||
우선순위1 11개 중 7개는 해소됐지만 4개(1-3/1-4/1-10/1-11)가 열려있었음.
|
||||
사용자가 이번 세션에서 4개 전부에 대해 구체적인 결정을 한 번에 제시.
|
||||
|
||||
## 1-3. 우선순위 스캔 동률/매치 실패 처리
|
||||
|
||||
사용자 판단: 동률(같은 `priority`)을 엄격한 tiebreak 규칙(예: 등록 순서)으로
|
||||
잡으려 하면 `None`/`Store`처럼 이미 `isHandlable`이 안 겹치는 내장 핸들러까지
|
||||
전부 그 규칙을 신경 써야 하고, 나중에 서드파티 핸들러가 늘어나면 계속
|
||||
골치아파짐. 대신 **목적별로 이름 붙은 우선순위 상수**(`HANDLER_PRIORITY_HIGH`
|
||||
등, 열린 숫자 공간 위의 편의 상수라 `+1`류 세밀 조정도 가능 — "우선순위
|
||||
밴드+오프셋"은 여러 업계에서 흔한 패턴)로 애초에 동률이 잘 안 나오게 유도.
|
||||
실제로 동률이 나면 대개 설계 실수이므로, 강제 규칙보다 **디버그 모드**(핸들러
|
||||
등록/정렬 시점에 동률 감지 시 print 경고, `Dispatch.listHandlers()`류 전체
|
||||
핸들러 목록 조회 함수)로 대응 — 구현 비용이 거의 없고 실제 개발에 바로
|
||||
도움이 되는 항목이라 M2 기본 기능으로 확정.
|
||||
|
||||
## 1-4. provider(팩토리) 미주입 상태에서 dispatch 호출
|
||||
|
||||
사용자 판단: 별도 처리 불필요. 핸들러가 없으면(`isHandlable`을 만족하는
|
||||
핸들러가 하나도 없으면) `Brand`/`typeof(v)`를 출력하고 그냥 에러로 죽으면
|
||||
됨 — "quad-roblox 등 provider가 초기화됐는지 확인하라"는 안내 문구 정도만
|
||||
추가. 이게 1-3의 "매치 실패=즉시 error" 규칙과 정확히 같은 경로라는 걸
|
||||
이번 세션에 확인 — provider 미주입 상태는 결국 "그 클래스의 핸들러가
|
||||
레지스트리에 없는 상태"이므로 매치 실패와 별개 케이스가 아님. 오타 키/
|
||||
미지원 조합/provider 미주입을 서로 다른 에러로 구분할 필요 없음(다른
|
||||
라이브러리에서도 흔한 패턴).
|
||||
|
||||
## 1-10. `store.key` 레코드 필드 타이핑
|
||||
|
||||
사용자가 Luau `type function`(https://luau.org/types/type-functions/,
|
||||
https://luau.org/types-library/) 기반 구체적 구현 방법을 제시 — `T`가
|
||||
테이블 타입이면 `ty:properties()`로 각 필드를 순회하며 `Source`로 감싼
|
||||
새 타입을 조립하는 `WrapStore`/`ProcessStoreType` 타입함수 스케치.
|
||||
`ProcessStoreType<{ty: string}>` → `{ty: Source<string>}`가 나오는데, 이건
|
||||
선언 시점에 이름 붙은 `Source<string>` 자체가 아니라 구조를 풀어낸 익명
|
||||
타입이지만 Luau가 이름이 아니라 "만족하는가"로 구조적 일치를 검사하므로
|
||||
문제없이 대입 가능 — 오히려 이 방식과 정확히 맞는 조합이라는 걸 확인.
|
||||
`type function`은 TBox에서도 이미 쓰이는 검증된 패턴. 이걸로 "검증 난이도"
|
||||
자체가 사라져 M0/M3 어느 시점에 실제로 부딪혀봐도 막힐 위험 없음 —
|
||||
`ROADMAP.md`의 M0/M3 배치를 억지로 옮길 필요는 없어짐.
|
||||
|
||||
## 1-11. `table.clone` + `__index` 메타테이블 트릭
|
||||
|
||||
사용자가 `table.clone`의 정확한 동작을 직접 확인해줌: 새 빈 테이블을 만들고
|
||||
원본 키를 네이티브 슬롯 단위로 얕은 복사(깊은 복사 아님), 그 다음 원본의
|
||||
`getmetatable` 결과를 그대로 새 테이블에 `setmetatable` — **메타테이블
|
||||
자체는 복사되는 게 아니라 같은 참조를 공유**. 이걸로 M7 전체 설계가
|
||||
의존하던 두 Luau 동작(제네릭 `__index`가 임의 key를 잡아 즉석 클로저를
|
||||
만드는 것, `table.clone`이 메타테이블 참조를 보존하는 것) 모두 공식
|
||||
동작대로 확인됨.
|
||||
|
||||
**추가 논의 — Property에 Attribute식 소유권 레지스트리를 적용하는 안,
|
||||
검토 후 기각.** 사용자가 "프로퍼티도 Attribute처럼 처리해도 될까" 생각해봤다가
|
||||
스스로 기각한 근거를 공유: Attribute 이름은 호출자가 자유롭게 짓는
|
||||
네임스페이스라 자기 전용 키 객체를 만들 수 있지만, Instance 프로퍼티
|
||||
이름은 엔진이 이미 정해둔 유한 집합이라 호출자가 전용 키를 새로 못 만듦.
|
||||
그래서 "이 프로퍼티를 지금 누가 소유하고 있는가"라는 질문 자체가 원천적으로
|
||||
성립 안 함(여러 modifier가 같은 프로퍼티를 건드리는 게 오히려 정상 시나리오,
|
||||
테마 오버라이드 등) — 이게 Property가 소유권 추적 대신 Modifier의 override
|
||||
우선순위 패턴을 쓰는 이유라는 걸 명문화.
|
||||
|
||||
## 결과
|
||||
|
||||
- `base/bind-system-plan.md`: "우선순위 동률/매치 실패 처리" 절 신설(핸들러
|
||||
계약 절), "`store.key` 레코드 필드 타이핑" 절 신설(타입 추론 문제 절
|
||||
바로 뒤).
|
||||
- `base/module-lifecycle-plan.md`: provider 미주입 케이스 해소 표시 추가.
|
||||
- `base/modifier-plan.md`: `table.clone` 정확한 동작 절 추가, Property
|
||||
소유권 레지스트리 기각 절 추가.
|
||||
- `research/pre-implementation-audit.md`: 1-3/1-4/1-10/1-11 전부
|
||||
`[해소됨]` 표시, "다음 액션 제안" 절 갱신 — **우선순위1 11개 전부 해소**.
|
||||
- `.claude/question.md` 2번 절 동기화.
|
||||
|
||||
M0 착수 전 남은 유일한 게이트는 `.claude/luau-test/` 스파이크 실측
|
||||
결과 확인(아직 사용자가 안 돌려봄) — 설계 자체는 더 이상 막힌 게 없음.
|
||||
|
||||
## 핸드오버 점검(같은 세션, 후속) — 누락된 스파이크 파일 발견
|
||||
|
||||
사용자가 "세션 중 알게된 지식 누락/stale 점검" 요청 — 위 문서 반영을 다시
|
||||
훑던 중, 1-10/1-11은 `pre-implementation-audit.md`/`base/`에 "확인됨"으로
|
||||
표시했지만 실제로는 **사용자의 설명/지식을 바탕으로 한 설계 레벨 확인이지,
|
||||
실제 Luau 코드로 돌려본 적은 없다**는 걸 재확인. 그런데 `.claude/luau-test/`
|
||||
15개 파일 중 이 둘(Luau `type function`으로 `store.key` 타이핑, 제네릭
|
||||
`__index`+`table.clone` 체이닝)을 커버하는 스파이크가 하나도 없었음 —
|
||||
둘 다 이번 세션에 처음 나온 구체 메커니즘이라 이전 스파이크 라운드
|
||||
(1~5차)에 있을 수 없었던 게 당연하지만, 그렇다고 "실측 대상"이라고만
|
||||
말해두고 실제 파일을 안 만들어두면 다음에 또 놓치기 쉬움.
|
||||
|
||||
**조치**:
|
||||
- `.claude/luau-test/16-type-store-key-typefunction.luau` 신규(타입체크
|
||||
전용) — 사용자가 준 `WrapStore`/`ProcessStoreType` 스케치를 그대로
|
||||
옮김, `luau-analyze`/`luau-lsp`로 실측.
|
||||
- `.claude/luau-test/17-modifier-index-tableclone-chaining.luau` 신규
|
||||
(런타임) — 제네릭 `__index` 체이닝(A), 메타테이블 참조 동일성(B),
|
||||
원본 불변(C), 형제 분기 비오염(D) 네 갈래로 assert.
|
||||
- `.claude/luau-test/README.md` 파일 목록 표/실행환경 표/"결과 확인 후
|
||||
할 일"/갱신 이력(6차) 갱신.
|
||||
- `ROADMAP.md`도 같이 감사 — M2에 우선순위 동률/매치실패 처리(디버그
|
||||
모드 `Dispatch.listHandlers()` 포함) 체크리스트 항목이 통째로 빠져있던
|
||||
것, M3/M7의 `store.key`/`table.clone` 항목에 새 스파이크 파일 링크가
|
||||
없던 것을 같이 보강.
|
||||
- `pre-implementation-audit.md`의 1-10/1-11 해소 블럭과 "다음 액션 제안"에
|
||||
새 스파이크 파일 이름을 명시적으로 링크.
|
||||
|
||||
**교훈**: "설계는 확정됐다"와 "실제로 실행해서 확인했다"를 같은 `[해소됨]`
|
||||
마커로 뭉뚱그리면, 정작 실측이 필요한 지점이 조용히 새는 패턴이 생김 —
|
||||
앞으로 `[해소됨]` 표시를 붙일 때 그 해소가 (a) 순수 설계 결정인지 (b) 실측
|
||||
검증까지 끝난 것인지 구분해서 적을 것.
|
||||
38
CLAUDE.md
38
CLAUDE.md
|
|
@ -111,21 +111,16 @@ modifier/Ref의 컴포넌트 경계 통과 방식) 논의도 2026-08-04 세션
|
|||
## 지금 할 일 (우선순위순)
|
||||
|
||||
1. **구현 시작 — 루트 `ROADMAP.md`의 M0부터.** 설계 단계는 2026-08-04 로드맵
|
||||
인수인계 라운드로 종료. M0 착수 직전 확인할 것 둘:
|
||||
- **`.claude/luau-test/`(2026-08-09 신설) 스파이크 결과** — 아직 사용자가
|
||||
인수인계 라운드로 종료. `research/pre-implementation-audit.md` 우선순위1은
|
||||
2026-08-12 열일곱 번째 세션에 마지막 넷(1-3/1-4/1-10/1-11)까지 전부
|
||||
해소되어 **11개 전원 완료** — M0 착수 전 남은 유일한 게이트는:
|
||||
- **`.claude/luau-test/`(2026-08-09 신설, 2026-08-12 열일곱 번째 세션에
|
||||
16/17 추가로 총 17개) 스파이크 결과** — 아직 사용자가
|
||||
`luau`/`luau-analyze`/`luau-lsp`/Roblox Studio로 안 돌려봄, 결과가
|
||||
나오면 그것부터 반영할 것(`luau-test/README.md`가 파일별로 뭘 우선
|
||||
확인해야 하는지 이미 적어둠). 걸리는 게 있으면 `base/` 문서부터 고치고,
|
||||
없으면 그대로 M0 실제 코드 작성에 재사용.
|
||||
- **`research/pre-implementation-audit.md` 우선순위1** — 신설 당시
|
||||
11개였으나 대부분 이후 세션에서 해소됨(Tween/Tag 재설계, Slot CRUD,
|
||||
`canExecute` 시그니처, `LifetimeHandle` 로드맵 순서 등). **[2026-08-12
|
||||
감사 세션 정정]** 실제로 아직 열려있는 건 넷(원문에 `[해소됨]` 마커가
|
||||
없는 항목 기준) — 우선순위 스캔 동률/매치실패 처리(1-3), provider
|
||||
미주입 상태 dispatch 처리(1-4), `store.key` 레코드 필드 타이핑을 M0로
|
||||
앞당길지(1-10), Modifier `__index`+`table.clone` 트릭 검증(1-11).
|
||||
이전엔 "1-3 하나뿐"이라 잘못 축약돼 있었음(1-4/1-10/1-11 누락) —
|
||||
`.claude/question.md` 2번이 최신 상태.
|
||||
없으면 그대로 M0 실제 코드 작성에 재사용. 설계 자체는 더 이상 막힌
|
||||
게 없음 — `.claude/question.md` 2번이 최신 상태.
|
||||
2. **용어 정리 — 1차 제안 이후 대부분 확정, 소수만 남음.** 최신 소스는
|
||||
`.claude/question.md` 1번(개수 반복 안 함, 항목 추가/해소될 때마다 여기가
|
||||
stale해지는 패턴이 반복됐어서). 아직 열려있는 것만 짚으면: `State`(1순위,
|
||||
|
|
@ -668,3 +663,22 @@ transitively 생존). `and`/`or` 삼항 관용구 전면 금지(기존 "항상-t
|
|||
예외" 조항 폐기), 코퍼스 전체 실제 코드 6곳을 `if-then-else`로 교체.
|
||||
Attribute 자동 unset이 필요해지면 쓸 `:Apply` opt-in 유틸을
|
||||
`research/operator-sugar-plan.md`에 백로그로 추가(착수 안 함).
|
||||
|
||||
**2026-08-12 열일곱 번째 세션 — 우선순위1 마지막 넷 전부 해소**
|
||||
(`session/2026-08-12-17-priority1-audit-resolved.md`)
|
||||
`pre-implementation-audit.md` 우선순위1 중 열려있던 마지막 넷을 사용자가
|
||||
한 번에 확정: 우선순위 동률/매치실패(1-3, tiebreak 강제 대신
|
||||
`HANDLER_PRIORITY_*` 상수+디버그 모드 동률 감지/핸들러 목록 함수, 매치실패는
|
||||
즉시 error), provider 미주입 dispatch(1-4, 매치실패 규칙에 자연 흡수),
|
||||
`store.key` 레코드 필드 타이핑(1-10, Luau `type function`으로 `Store<T>`→
|
||||
`{[K]: Source<V>}` 합성 가능함을 구체 스케치로 확인), Modifier
|
||||
`__index`+`table.clone` 트릭(1-11, 메타테이블이 복사 아닌 참조 공유임을
|
||||
확인). 부수적으로 Property에 Attribute식 소유권 레지스트리를 적용하는 안을
|
||||
검토 후 기각(엔진이 정한 유한 프로퍼티 이름 집합은 호출자가 전용 키를 못
|
||||
만들어 소유권 판정 불가 — Property가 override 우선순위를 쓰는 이유).
|
||||
**우선순위1 11개 전원 해소** — M0 착수 전 남은 건 `.claude/luau-test/`
|
||||
스파이크 실측뿐. **핸드오버 점검 중 발견**: 1-10/1-11은 설계는 확정됐지만
|
||||
이를 실제로 실측할 스파이크 파일 자체가 없었던 갭 — `16-type-store-key-
|
||||
typefunction.luau`/`17-modifier-index-tableclone-chaining.luau` 신규
|
||||
추가(총 17개), `ROADMAP.md` M2/M3/M7 체크리스트에도 누락됐던 항목(디버그
|
||||
모드 동률 감지+`listHandlers`, `store.key`/`table.clone` 실측 링크) 보강.
|
||||
|
|
|
|||
21
ROADMAP.md
21
ROADMAP.md
|
|
@ -137,6 +137,14 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
걸러내기(no-op이라도 필드 자체는 항상 정의 — `Dispatch.process`가 핸들러
|
||||
교체 시 nil 체크 없이 호출, `base/bind-system-plan.md` "핸들러 계약"
|
||||
절, 2026-08-08 세션)
|
||||
- [ ] 우선순위 동률/매치 실패 처리(2026-08-12 열일곱 번째 세션 확정,
|
||||
`base/bind-system-plan.md` "우선순위 동률/매치 실패 처리" 절) —
|
||||
`HANDLER_PRIORITY_HIGH`/`_NORMAL`/`_LOW` 등 목적별 우선순위 상수,
|
||||
매치 실패(`isHandlable`을 만족하는 핸들러 없음)는 `Brand`+`typeof(v)`
|
||||
출력 후 즉시 error(provider 초기화 확인 안내 포함 — provider
|
||||
미주입 상태도 이 경로로 자동 커버, `pre-implementation-audit.md`
|
||||
1-3/1-4), 핸들러 등록/정렬 시점 동률 감지 print 경고 +
|
||||
`Dispatch.listHandlers()` 디버그 유틸
|
||||
- [ ] `Dispatch/Leaf.luau` — `(i:number, v=Ref/Observer/PreRef)` children-array
|
||||
leaf 매칭 Handler, `StoreBind.luau`와 같은 층위(범용/엔진무관) —
|
||||
quad-base 소속으로 확정(2026-08-08 두 번째 세션, `base/
|
||||
|
|
@ -153,7 +161,12 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
## M3 — Store/State/Source
|
||||
|
||||
- [ ] `Source.luau`/`State.luau`/`Store.luau`
|
||||
- [ ] `store.key` dot-access 타입 추론 확인
|
||||
- [ ] `store.key` dot-access 타입 추론 확인 — Luau `type function`
|
||||
(`WrapStore`/`ProcessStoreType`)으로 `Store<T>`가 `T`의 각 필드를
|
||||
`Source`로 감싼 레코드 타입을 합성 가능함을 확인(2026-08-12 열일곱
|
||||
번째 세션, `base/bind-system-plan.md` "`store.key` 레코드 필드
|
||||
타이핑" 절) — 실제 문법이 통과하는지는
|
||||
`.claude/luau-test/16-type-store-key-typefunction.luau`로 실측 필요
|
||||
- [ ] `:Compute(fn, ...)` — trailing args로 추가 의존성 직접 받는 sugar
|
||||
(2026-08-11 세션, `base/bind-system-plan.md` "`:Compute(fn, ...)`"
|
||||
절) — `:With(...):Compute(fn)` 체인과 달리 노드 1개(Compute 노드
|
||||
|
|
@ -369,7 +382,11 @@ Luau 코드로 부딪혀본 적 없는 세 가지**를 던지는 코드로 검
|
|||
`Type(args)` 팩토리 관습, `modifier-plan.md` 3번)
|
||||
- [ ] flatten-before-dispatch(`isModifier(v)`로 배열 항목 중 Modifier만
|
||||
판별해 필드 merge, 나머지는 안 건드리고 통과 — 2026-08-07 열 번째
|
||||
세션 명시, `modifier-plan.md` 1번), immutable `table.clone` 체이닝
|
||||
세션 명시, `modifier-plan.md` 1번), immutable `table.clone` 체이닝 —
|
||||
`table.clone`이 메타테이블을 복사 아닌 참조로 공유해 제네릭 `__index`
|
||||
기반 체이닝이 안 끊긴다는 메커니즘은 확인됨(2026-08-12 열일곱 번째
|
||||
세션, `modifier-plan.md` "`table.clone`의 정확한 동작" 절) — 실제
|
||||
Luau 실행 확인은 `.claude/luau-test/17-modifier-index-tableclone-chaining.luau`
|
||||
- [ ] `Modifier.Overridden(mod1, mod2, ...)`(이름 확정, 구 `Merge`→`Override`,
|
||||
2026-08-08 세션) — 필드별 raw 덮어쓰기, 특별한 State/함수 분기
|
||||
불필요(`modifier-plan.md` 9번)
|
||||
|
|
|
|||
Loading…
Reference in a new issue