- §0 회신 기록: Q1(M2 골격 + Handler 체크리스트 게이트)/Q2(단위 넷)/ Q3(M2 하자는 규모로 가름) 전부 권고 (a) 사용자 채택 - §6 신설·승인: Dispatch/Handler.luau(타입 전용 잎) + Dispatch/init.luau (InitDispatch(module), chains는 인스턴스별 Relate, HANDLER_PRIORITY_* 상수 정의+재노출) + quad-types Dispatch 필드(H-25) / drive는 단위 1에서 (b) 본체 루프만 — 배치 게이팅은 단위 2, pre-pass·postRefList는 M8 / 스파이크 01 재작성은 spec.drive.luau 상시 회귀로 대체 - round12.md 스텁을 실 스켈레톤(요약 표/§4/§5/§6)으로 교체, H-212부터 - 인덱스 3층 갱신: README qa-request 행, CLAUDE.md·project-context 머리말, todos.md 00번 — "M3 진행 중" Co-authored-by: qwreey <me@qwreey.moe> Claude-Session: https://claude.ai/code/session_01LF78pXeFGD1ZSVD3ifteYG
13 KiB
M3 자율 구현 규약 — 12라운드 지시서 + 착수 문항지
이 파일이 무엇인가: [2026-08-31 신설, 같은 날 §0·§6 회신 완료 — 규약 확정] M3(디스패치 엔진) 구현 구간의 규약이자 착수 문항지다. §0 세 문항과 §6 첫 단위 계획이 전부 확정돼(회신 기록은 §0 바로 아래), 이 파일은 M2의
m2-implementation-round11-brief.md와 같은 지위(규약 소스 + 단위 끝 탐사자 지시서)다. 산출물(발견 문서)은m3-implementation-round12.md.명명:
mN-implementation-roundNN규약(2026-08-31 사용자 확정,.claude/README.mdqa-request/행이 소스) — 라운드 번호는 마일스톤을 가로질러 단순 증가라 M3의 첫 라운드가 round12, 발견 번호는 round11이H-211까지 썼으므로H-212부터.§2~§5는 M2 규약을 준용하되 M3에 맞게 고친 자리에
[M3 변경]표시를 달았다 — M2와 어디가 다른지는 그 표시만 훑으면 된다.
§0 ⭐ 착수 문항 (배치 회신용)
| 문항 | 무엇 | 선택지 | 권고 | 권고 근거 |
|---|---|---|---|---|
| Q1 | M2 규약의 재사용 범위 | (a) 골격 그대로 — 발견 세 갈래(§2) / 리뷰 발견 취급(§3) / 커밋 게이트 두 층·단위 끝 절차(감사 루프 → /code-review high → 커밋 → fable 탐사자 → "§4를 보라" 한 줄)(§4) — 에 M3 전용 게이트 하나 추가: 새 핸들러를 짜기 전 dispatch-core-plan.md의 "Handler 작성 체크리스트" 절 필독(실제 반복된 실수 목록이라 M2엔 대응물이 없던 것) / (b) 절차를 줄임(예: 탐사자 생략) / (c) 다른 방식 |
(a) | M2 실측 — ③(즉시 중단) 0회, 배치 회신 3번으로 발견 47건(H-165H-211) 처리, 단위 절단 덕에 관여 시점이 촘촘해 손실 구간이 없었다. 탐사자는 매 단위 ①만 내고도 문서-코드 어긋남을 7건(H-191H-197) 잡은 실적이 있어 유지 가치가 실증됨 |
| Q2 | 단위 절단 | (a) 넷 — §1의 제안(코어 → 부기 → None/Nil 핸들러 → Leaf·가드·종합) / (b) 셋(코어+부기 합침 — 단위당 비용 커짐) / (c) 다른 절단 |
(a) | 의존이 단방향(코어 → 부기(NilHandler가 부기 API를 등록) → 핸들러 → 가드·종합)이고, M2와 단위당 규모가 비슷해 비용 감각이 검증된 눈금 그대로다. 단위 2가 M2 소비의 첫 실전(Observer 콜백·Blocker·접두합 캐시)이라 M2 결함이 있다면 일찍 드러난다 |
| Q3 | M3 진행 중 M2 하자가 나올 때 | (a) 경미한 것(문서 stale·주석·기존 계약 안의 코드 오류)은 M3 라운드 파일(m3-implementation-round12.md)에 H-nnn으로 기록하고 ① 갈래로 자율 수정, M2 설계 결정이 필요한 규모(새 메커니즘·확정 역전)면 그때 m2-implementation-round13(그 시점의 다음 번호)을 새로 열어 §4 배치 문항으로 / (b) 규모 무관 전부 M3 파일에 / (c) 규모 무관 전부 M2 파일 신설 |
(a) | 명명 규약의 취지("m3 을 진행하다 m2 에 하자가 있음을 확인하면 다시 m2 로 올라가 round 가 진행되다 돌아오는 경우") 그대로 — 접두가 소속을 담으려면 결정이 필요한 것만 M2 라운드로 승격하고, 잔손질까지 파일을 쪼개면 발견 흐름이 갈라진다 |
⭐ [2026-08-31 회신 — 전량 확정] 사용자가 대화형 선택지로 Q1·Q2·Q3 전부
권고 (a)를 채택했고, §6 첫 단위(코어) 계획도 그대로 승인했다("승인 —
이대로 착수" 선택; 세션은 사용자 지시 *"M3 를 작업 시작하자.
m3-implementation-round12 문서를 보면 돼"*로 열렸다). 이로써 이 파일이 M3
규약 소스다 — 발견 번호 H-212부터, M2 하자는 Q3 (a) 규칙.
통보(문항 아님 — 규약·코드 배치라 여기 명시만): 발견 문서는 착수 시
m3-implementation-round12.md 신설, 번호 H-212부터. mock 확장은 불필요 —
M3 핸들러가 부르는 건 quad-base 자기 부기 API(setLength 등)뿐이고 엔진
op(addTag/setAttribute/native*)는 M5/M10 몫이라 지금 mock(생명주기 4종 +
onDestroying + mock Instance)으로 충분하다. 코어 테스트는 실핸들러가 아직
없으므로 spec 파일 로컬의 테스트 전용 핸들러로 돌린다(코드 배치).
§1 범위와 순서 — 단위 넷 (제안, Q2)
소스는 ROADMAP.md M3 체크박스(13개, 개수·상세는 거기가 소스). 단위 안의
순서는 체크박스 순서를 따르되 단위 배정은 아래:
- Handler 계약 + 디스패치 코어 —
Handler.luau(계약 타입 3종:isHandlable(inst,k,v)/priority/process(...) -> retractor),Dispatch/init.luau(getHandler/process(하강 diff)/3-인자retractFrom/addHandler/drive단일 일반화for),chains부기 (Relate 기반), 계약 검증(process가 retractor를 안 돌려주면 즉시 error), 우선순위 동률·매치 실패 처리,quad-typesQuad.Dispatch(H-25). 파이프라인 정본은base/dispatch-core-plan.md와base/bind-system-plan.md의 "New(name)(props)파이프라인 의사코드" 절. - Length/Offset 부기 —
setLength(+anchor/element인자)/setOffsetSource/getOffsetAt+ 접두합 캐시 계약 전체(bk.offsetCache/offsetCacheValidUpTo/offsetSetUpTo두 필드/recomputeBlocker/bk.indexOfElementweak-key, 무효화 자리는dispatch-core-plan.md의 무효화 표가 소스), len이 State일 때의 Observer 콜백. M2 소비의 첫 실전(Observer·Blocker·Source). None센티널 +NoneHandler/NilHandler— 첫 실핸들러 둘.H-39(말단 핸들러의setOffsetSource(None)→setLength(0)등록 계약)의 첫 적용이자 Handler 작성 체크리스트 게이트의 첫 실행.architecture.md소스 트리에 탑레벨None.luau줄도 이때 추가(ROADMAP가 예고).Dispatch/Leaf.luau(ObserverEffectLeafHandler) + Observer/Effect 동적 경로 가드 등록 + mock 대상 종합 테스트(ROADMAP M3 마지막 항목) — M2 값(Observer/Effect/bindLifetime)을 디스패치가 실제로 물어 올리는 자리.
- 각 모듈은 M2처럼 "
base/확정 의사코드를 그대로 옮긴 구현 + 그 절의 계약을 검증하는 spec" 짝으로, 테스트는./scripts/test.sh로만. - [M3 변경] 이름 해석 규칙 하나:
base/의사코드의 자유 이름 (bindLifetime/canExecute/Effect/Blocker등)은 전부InitDispatch(module)이 쥔module.xxx이고 생명주기 넷은 발화 시점에 늦게 읽는다(H-174(a) — round11 §5의 M3 예고가 소스). - [M3 변경] 미리 알려진 주의 둘(round11 §6):
H-186(교차 인스턴스 UB)의 이웃인 교차bindLifetime은 UB로 닫혔으니 가드를 새로 만들지 말 것 / Slot 쪽(materializeSlotTree)이 Observer 사적 필드를 직접 쓰는 자리는 M6 몫이되 그 경계가 이 마일스톤 코드에 들어오면 발견으로 올릴 것.
§2 세 갈래 (M2 §2 준용)
m2-implementation-round11-brief.md §2 그대로 — ① 문서가 답을 가진 것은
H-nnn 기록 + base/·코드 같은 커밋 자율 수정 / ② 새
필드·인자·이름·메커니즘·확정 역전은 코드에 넣지 말고
m3-implementation-round12.md §4 표에 갈래+권고+근거로 배치(코드엔
-- TODO(H-nnn) 마커만, "옛 메커니즘 복원" 표시 포함) / ③ 짠 코드 상당
부분을 무효화할 규모면 즉시 중단·보고. [M3 변경] M2 하자는 §0 Q3의
결정을 따른다.
§3 리뷰·감사 발견의 취급 (M2 §3 준용)
그대로 — 리뷰의 "새 메커니즘"은 ②로, 반영 전 소유자·기각 이력 grep.
§4 관여 시점 (M2 §4 준용)
커밋 게이트 두 층(매 커밋 doc-check ERROR 0 / 단위 끝 감사 루프 →
/code-review high → 커밋 → fable 탐사자 → "§4를 보라" 한 줄), 세션 원문
규율, 인덱스 3층 갱신 — 전부 그대로. [M3 변경] 머리말 갱신 문구는
"M3 진행 중".
§5 탐사자 지시 (단위 끝마다)
M2 §5를 그대로 쓰되 치환 셋: 대상 라운드 파일은
m3-implementation-round12.md, 이 파일의 §1~§3을 먼저 읽고, 대조 대상
base/ 절은 dispatch-core-plan.md(특히 "Handler 작성 체크리스트" 절과
무효화 표)·bind-system-plan.md가 중심. git stash 금지·실행 우선·한 줄
대조·grep -rn "TODO(H-" 전수 확인은 동일.
§6 첫 단위(코어) 작업 계획 — [2026-08-31 사용자 확정] ("승인 — 이대로 착수")
여기 적힌 것은 base/가 정하지 않은 코드 배치·범위 절단이라 이 계획이
소스다(M2 §6과 같은 지위) — 설계 결정이 아니다.
소스 (quad-base/src/, 배치는 architecture.md 소스 트리 그대로)
| 파일 | 내용 | 옮겨 적는 절 |
|---|---|---|
Dispatch/Handler.luau |
계약 타입만 담는 잎 — Handler 타입(isHandlable(inst,k,v): boolean / priority: number / process(inst,k,v,index) -> (any?) -> ()). Dispatch를 되참조하지 않는다 |
dispatch-core-plan.md "핸들러 계약" 절, "Dispatch는 프리미티브가 아니다" 절의 단방향 의존 |
Dispatch/init.luau |
InitDispatch(module) 팩토리(module-lifecycle-plan.md "New()의 내부 구성" 예시 그대로 — H-174 (a)) — module.Dispatch에: getHandler(우선순위 스캔·첫 매치, 매치 실패는 Brand+typeof(v) 출력 + provider 안내 즉시 error, 실패 메시지는 클로저 지연 생성) / process(하강 diff (A)/(B) 의사코드 그대로 — retractor nil 반환 즉시 error 양쪽, (B) 점유 마커 선행, chains:SetStrong이 h.process 전, (A) 소비 직후 NOOP 교체) / 3-인자 retractFrom(꼬리 역순, 구멍이면 error) / addHandler(등록 시점 정렬, 동률 감지 print는 module.debug가 참일 때만) / listHandlers(항상 호출 가능, 반환만 하는 순수 조회) / drive(범위는 아래 행). chains는 별도 파일이 아니라 InitDispatch가 만드는 Relate 인스턴스별 값. HANDLER_PRIORITY_HIGH/_NORMAL/_LOW/_FALLBACK 상수는 이 파일 정의 + module.Dispatch에 재노출(Handler.luau를 타입 전용 잎으로 유지) |
"Dispatch 체인" 절 의사코드(H-103 주석 포함), "우선순위 동률/매치 실패 처리", H-162(NOOP = Void) |
(범위 절단) drive |
이 단위에선 파이프라인 (b) 본체 단일 일반화 for만(각 (k,v)에 process(inst,k,v,1)). ⓪/⓪' 배치 Blocker 게이팅은 단위 2(getBlocker/getBookkeeping/recompute가 생기는 자리)에서 배선, (a) pre-pass/(c) postRefList는 M8(PreRef/PostRef 본체·소진 센티널이 생기는 자리). 정본과의 차이는 코드 헤더 주석에 명시 |
bind-system-plan.md "New(name)(props) 파이프라인 의사코드" 절 |
quad-types/src/init.luau |
Quad 타입에 Dispatch 필드 + Handler 타입 재수출(H-25 — 마일스톤마다 갱신 규칙의 M3 몫) |
quad-types-plan.md "Quad 타입 — 확정된 표면" 절 |
init.luau(최상위) |
module:RunInit(InitDispatch) 추가 |
module-lifecycle-plan.md |
테스트 (quad-base/test/) — 테스트 핸들러는 §0 통보대로 spec 파일 로컬
(실핸들러가 아직 없음). inst는 평범한 테이블(base 계약상 inst는 백엔드
재량이라 mock 확장 불필요).
| 파일 | 검증하는 계약 |
|---|---|
spec.dispatch.luau |
getHandler 우선순위 스캔·첫 매치 · 매치 실패 error 메시지(Brand/typeof/provider 안내) · (A) 분기: retractor가 새 값을 받고 아래 체인은 안 건드림, 같은 자리 클로저 교체 · (B) 분기: 그 자리부터 꼬리 역순 철거 후 재설치 · retractor 반환 생략 즉시 error((A)/(B) 모두) · retractFrom 꼬리 역순·항상 소비(list[i]=nil)·구멍 error · 재귀 위임 index+1/다른 키 위임 항상 1 · 래핑 핸들러 2단 체인(같은 핸들러가 인덱스 N·N+1 — State<State<T>> 유사 구조를 spec 핸들러로) · SetStrong 선행(재귀 위임 시 하위 retractor 유실 없음 — 2026-08-13 감사 버그의 음성 대조) · 조건부 재위임 핸들러의 retractFrom(index+1) 직접 호출 경로(체크리스트 8) · addHandler 정렬·동률 경고가 module.debug 게이팅 · listHandlers 반환만 · New() 인스턴스 간 레지스트리/체인 격리 |
spec.drive.luau |
단일 일반화 for 한 번으로 배열 파트 전체가 해시 파트보다 먼저 + 배열 안에서는 index 순서(F-4-1 — 재작성 대기 스파이크 01이 물어야 했던 언어 동작 질문을 여기서 실측) · 모든 진입이 index 1 |
- 스파이크
01재작성(luau-test/rewrite-required/01-*)은 따로 안 한다 —spec.drive.luau가 같은 질문을 상시 회귀로 실측하므로.STATUS.md에 그 취지를 기록하고 재작성 대기 목록에서 정리(이 처분도 §0 회신에 묶임).
커밋 단위: M2와 같게 모듈마다 하나(구현 + spec + base/ 정정이 있으면
같은 커밋), 단위 끝 절차는 §4.