CLAUDE.md had grown to 3196 lines of accumulated session logs, causing context bloat. Full session narratives (including trial-and-error and later-corrected reasoning — quadnomicon devlog raw material) now live as 39 individual files under .claude/session/. CLAUDE.md keeps only a short "지금 할 일" (re-synced against question.md/pre-implementation-audit.md, stale detail dropped) and a compact per-session summary+link table. No design decisions changed; base/research/question.md were already in sync with every session (verified against README.md/question.md before archiving), so no unreflected content needed migrating first.
7.2 KiB
2026-08-08 세 번째 세션 — Tag를 array-part 값 객체로 재설계, Dispatch
체인+retractUnder로 재귀 재-dispatch의 retract 전파 문제 해결
같은 날 이어진 세션. 사용자가 "Tag를 해시 파트 boolean 키 대신 array-part
값 객체로 바꾸는 게 낫지 않냐"는 질문으로 시작 — 상호배타 스타일 상태
(btn1/btn2/btn3류, 20개까지도 가능)를 표현하려면 구 모델은 태그
개수만큼 키를 갱신해야 해서 끔찍하다는 실사용 근거. 이 논의가 "retract가
새 값의 타입에 따라 이전 핸들러를 정확히 찾아 부를 수 있는가"라는 훨씬
근본적인 구멍(pre-implementation-audit.md 1-2번이 이미 지적해뒀던 것)을
직접 건드리게 됐고, 몇 차례 시행착오 끝에 사용자가 제시한 "체인+
retractUnder" 설계로 수렴. 세 갈래로 정리:
1. Tag 재설계 — array-part 값 객체, Modifier와 같은 immutable clone
체이닝. Tag(name1, name2, ...)(가변인자 생성자, 빈 Tag()도 유효),
:Added/:Removed(뮤테이션처럼 안 보이게 -ed 어미 — 실제로는 항상
clone 후 반환), :Contains(name):boolean, :Apply(factory)(Modifier와
동일한 순수 체이닝 설탕), Tag.Merged(tag1,tag2,...)(집합 합집합, 무손실
— Modifier의 Override는 필드 단위 덮어쓰기라 손실 있음, 그래서 이름도
다름). None 센티널은 불필요로 확인 — 동적 토글은 Source/State가
계산 결과로 nil을 리턴하면 되는 함수 인자 전달이라 테이블 리터럴의
nil-hole 문제 자체가 없음(정적 리터럴에서 조건부로 Tag를 넣고 뺄 때는
다른 array-part 값과 마찬가지로 기존 None 관용구가 그대로 유효, Tag
전용 규칙 아님). 구 모델(해시 파트 boolean, "핸들러 타입이 안 바뀌니
retract 불필요"가 결론이었음)은 archive/tag-hash-key-model-reversed.md로
역전 보존, base/tag-plan.md 전면 재작성. 값 타입+API(Tag.luau)는
quad-base, CollectionService 글루(Handlers/Tag.luau)만 quad-roblox —
이미 확정된 "base는 인터페이스/값, backend는 process·retract 글루"
패턴(LifetimeHandle)을 값 타입 수준까지 그대로 확장한 것으로 확인,
새 아키텍처 개념 아님.
2. Tag 재설계가 "retract가 실제로 필요해지는" 첫 array-part store-bind
사례가 되며, 기존 "이전 핸들러 추적" 설계 공백이 정면으로 드러남.
pre-implementation-audit.md 1-2번이 이미 "store-bind 재실행 모델에서
realv 타입이 매 갱신마다 바뀔 수 있는데 '이전 핸들러'를 누가 추적하는지
불명"이라고 짚어뒀던 것 — Tag가 Tag(...)↔nil 사이를 오가며 실제로
핸들러 타입이 바뀌는 구체 사례가 되어 더 이상 미룰 수 없어짐. 시행착오
과정:
- 1차 제안(제가 냄, 기각됨): Dispatch가
(inst,k)별로 "지금 누가 담당 중인가"를 슬롯 하나로 추적. 재귀/래핑 핸들러(StoreBind 등) 에서 깨짐 — 사용자가 직접 "A→B 구조에서 A가 바뀌면 B의 retract가 실행되고, 재귀로 B로 다시 내려오면 retract가 없는 거 아니냐"고 반례를 제시 — A 자신의 생명주기(예: Observer 구독)와 A가 재귀로 위임한 B의 생명주기가 슬롯 하나를 두고 서로 덮어써서, A가 스스로 재-dispatch할 때 자길 엉뚱하게 retract하거나 반대로 안 해야 할 때 안 하는 오작동이 생김이 실제 트레이스로 확인됨. - 2차 제안(제가 냄, 부분 기각): 각 래핑 핸들러가 자기 전용
Relate에 위임 대상을 비공개로 저장(A→B→C면 A.retract가 수동으로 B.retract를 부르고 B.retract가 수동으로 C.retract를 부르는 linked 구조). 동작은 하지만 사용자가 두 가지 지적: (a) 나중에 재바인드(existing-instance- bind-plan.md) 지원을 생각하면 위임 정보가 핸들러별로 비공개 분산돼 있어 외부에서 못 들여다봄, (b) 각 핸들러 작성자가 "내 retract에서 위임 대상도 cascade해야 한다"는 걸 매번 기억해야 하는 규율 의존적 설계. - 최종 채택(사용자 제안) — Dispatch가
(inst,k)별 핸들러 체인(순서 있는 배열)을 직접 소유,Dispatch.retractUnder(inst,k,keep,v)가 꼬리부터keep앞까지 훑으며 정리.Dispatch.process가 매치될 때마다 체인에 push, 재귀/래핑 핸들러는 재-dispatch 전에retractUnder(inst,k,self,newV)를 먼저 불러 자기 밑을 정리 — 이 한 번의 루프가 다단 체인(A→B→C) 전체를 순서대로 정리해주므로 개별 핸들러의retract는 더 이상 자기 위임 대상을 수동으로 안 쫓아가도 됨(2차 제안의 (b) 해소), 체인이 Dispatch에 중앙화돼 있어 미래 재바인드도retractUnder(inst,k,nil,newV);process(inst,k,newV)두 줄로 자연스럽게 됨((a) 해소) — quad-debug의 "무엇이 무엇에 연결됐는가" 그래프도 이 구조를 그대로 읽으면 됨. 배열이 항상 꼬리에서만 추가/삭제되는 스택 모양이라None소진 이슈(구멍 있는 정수 키 순회 문제)도 애초에 안 생김.retract는 여전히(inst,k,v)3-인자 유지 — 한 차례 제가 "v 제거"를 제안했다가 틀렸음(사용자가 Tag의 전체삭제 vs diff 분기를 근거로 정정) — 다만 최종 설계에서 diff는process(같은 핸들러 유지 시)의 몫이고retract는 항상 "더 이상 매치 안 될 때만" 불리므로 Tag 한정으로는v를 안 봐도 항상 전체 삭제가 맞다는 것도 확인. 순환은 기존 "일반적 무한루프 방어 안 함" 원칙(2026-08-04) 그대로 UB.
3. 전부 base/bind-system-plan.md(신규 "Dispatch 체인" 절 + "확정된
디스패치 모델"/"None 센티널"/"Store 바인드는 특수 경우인가" 절 갱신)/
base/tag-plan.md(전면 재작성)/archive/tag-hash-key-model-reversed.md
(신규)/base/architecture.md(소스트리 Tag.luau 추가, 4번 항목 정정)/
ROADMAP.md(M2/M4/M10)/research/pre-implementation-audit.md(1-2번
해소 표시)에 반영 완료.
다음 세션이 할 일: 안 바뀜(ROADMAP.md M0부터). M2/M4 스파이크
검증 목록에 chains/retractUnder가 다단 체인에서 실제로 정확히
동작하는지가 새로 추가됨(추론만으로 확정된 것, pre-implementation-audit.md
류 "실제 Luau로 부딪혀본 적 없는 것" 범주). pre-implementation-audit.md
1-1번(Tween이 유일한 store-bind 예시라 "일반 store-bind와 Tween이 같은
핸들러인지"가 불명확한 문제)은 Tag가 두 번째 구체 사례가 되면서 정황상
"별개 핸들러, 둘 다 Dispatch/StoreBind.luau 재사용"쪽에 힘이 실리지만
아직 명시적으로 확정된 건 아님 — M2/M4 착수 전 마저 확인할 것.