사용자 요청("같이 하나하나 처리해나가보자. 질문 모드로 계속 물어보며")으로
발견 보고 `H-1`~`H-54`를 문항지로 만들지 않고 **갈래 선택이 필요한 것만 급한
순서로** 물어 전량 결정하고 `base/` 24개 문서에 반영했다. 결정과 근거는
`qa-request/pre-implementation-handtrace-round6-followup.md`가 소스이고,
진행 경위와 사용자 발언 원문은
`session/2026-08-24-01-handtrace-round6-resolution.md`.
**M2/M3를 막던 것이 전부 닫혔다** — 말단 핸들러 4종의 `setLength`/
`setOffsetSource` 미등록(`H-39`), `New(): Quad`가 닫힌 타입이라 `quad.Dispatch`가
타입에러인 것(`H-25`), `Effect`의 leaf 사망 cleanup 배선 부재(`H-11`),
`:List`의 좌표계 결함 둘(`H-1`/`H-2`).
## 구조가 바뀐 것 넷
- **`slot._elemIndex`**(물리 요소→인덱스 역방향 맵) 신설로 `indexOfRaw`가 O(1)
기본 경로가 되고, `:List`의 `keyIndex`는 **단순 키 집합(`prevKeys`)**으로
강등. 사용자 역제안 — *"raw* 가 층위를 알아야할 이유를 모르겠는 상태 …
realElem->index 해시맵을 만들어주고, index 밀고 당기는 동작에서 이걸 같이
업데이트해주는 편이"*. 맵을 `_elements`와 같은 층에 두니 층 분리가 오히려
깨끗해졌다
- **`_mounted`가 "물리 인스턴스 유무"만 뜻하게 좁혀지고 `slot._physicalTarget`
신설** — 상태가 셋이 됐다(미실체화/실체화/마운트). `raw*`는 부기를 실체화
시점부터 항상 하고 `native*`만 가른다. 그래야 최초 population 중
`getOffsetAt`이 성립해 `updateFn`의 `index`를 거기서 뽑을 수 있다
- **`Ref.Callbacks`가 해시맵 셋 + `:Uncallback`**(사용자 발견) — 해제가 O(1)이
되고 `ref-plan.md`의 `#t` border 실측 항목이 폐기됐다
- **`blocker:Policy(emit)` 노출** — `Debounce`/`Throttle`이 emit을 안 쥐고 자기
Blocker를 On/Off만 하는 정책이 된다. `Gate`엔 `Flush`/`Cancel`을 안 둔다
요소 타입 검증은 블랙리스트에서 **주입 술어 `isInst` 기반 화이트리스트**로
뒤집혔고(`H-40`), 주입 op이 둘 늘었다(`isInst`/`onDestroying` — 조합 폴백이
불가능해 미주입이면 에러).
## 사용자가 에이전트 갈래를 뒤집은 자리가 여럿
`H-1`(세 갈래가 전부 차선), `H-2`(*"부기 확정에서 length 를 확정해도 되는거
아님?"* — 부기와 물리 마운트를 분리하라는 되물음), `H-40`(브랜드 판정이
2026-08-21 인스턴스 브랜드 재작성 이후 성립 불가임을 지적), `H-33`(제 중첩
합성안이 unblock 시 디바운스 창을 새로 시작시켜 창이 안 끝난다는 지적).
`Ref.Callbacks` 해시맵화와 `Ref` 콜백의 `canExecute` 확인은 사용자가 먼저 발견.
## 반영 후 재검토 — `/code-review high` 7건 + 감사 9라운드 34건
**`/code-review high` 7건 중 셋이 이번 반영이 만든 회귀**였다 — 상태가 셋이
됐다고 산문에 쓰고 코드엔 경계 하나만 남겨 `Slot { frameA }` 생성자가
크래시하던 것, `native*`를 `_mounted`로 가리면서 그게 곧 파괴였다는 걸 놓쳐
영구 누수를 만든 것, "`:List`와 CRUD는 상호배타"라며 승인받은 분기가
**재마운트 경로를 안 봐서** 포탈을 깬 것. 셋 다 코퍼스 정합성 각도로는
구조적으로 안 보이는 종류라 *"`/code-review`는 감사자를 대체하지 않는다"*가
실측으로 재확인됐다. 같은 리뷰가 `H-11`의 두 결정이 서로 모순임을 잡아
재결정했다 — *"`EffectHandle`이 자기 `bindLifetime` 직후에 건다"*는 **그 호출부가
실재하지 않았고**, 사용자 판단으로 `bindLifetime`/`unbindLifetime`이 `isEffect`를
보고 직접 처리하는 것으로 바뀌었다.
**`quad-doc-auditor` 감사 루프는 9라운드에서 새 발견 0건으로 수렴**(라운드별
5→7→2→2→3→9→2→4→0, 각도와 목록은 followup의 E절). 한 턴에 하나씩 돌리고
라운드마다 각도를 바꿨다. 가장 많이 잡은 6라운드(9건)는 *"이 체크박스로 코드를
짜면 무엇이 나오는가"*를 물은 라운드였고, 그때 `ROADMAP.md`의 미완료 항목이
대거 stale인 게 드러났다(폐기된 `pos` 공식이 "확정"으로, 접두합 캐시 무효화
계약이 통째로 부재, `bindLifetime`이 "둘만 한다"고 적혀 `H-11`과 직접 모순).
**반복된 실패 패턴은 하나 — "고쳐야 할 자리가 N개인데 일부만 고쳤다"**:
배너를 달고 그 배너가 부정하는 문장을 안 고침(1라운드), `base/` 19개를 바꾸고
`.claude/README.md`를 한 줄도 안 고침(2라운드), 그 README를 고칠 때 11행 중
6행만(7·8라운드). 셋 다 핸드오버 체크리스트가 명시적으로 경고하는 항목이라,
규율이 없어서가 아니라 지켰는지 스스로 확인하지 않아서 생긴 실패다.
## 백로그 하나
`Fallback`/`Traceback` 중 생성된 부분 트리의 회수(`H-26`) — 그 둘이 슈가라
구현 시점에 같이 다룬다. 같이 확인된 것: `dispatch-core-plan.md`가 잔여 부기를
인스턴스 GC가 정리한다고 적은 문장은 **gcconn 불멸성과 양립하지 않는 틀린
안전망 주장**이라 삭제했다.
**M2 착수를 막는 설계 항목은 이제 없다** — 남은 건 `question.md` 2번(M2↔M3
양방향 의존, 마일스톤 순서)뿐이다. `doc-check.py` ERROR 0.
Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01Jjrec9xAS7TZstMx5gi3cm
152 lines
9.7 KiB
Markdown
152 lines
9.7 KiB
Markdown
# 2026-08-24 — 6라운드 손 트레이싱 전량 처리·반영, 그리고 세 번의 재검토
|
|
|
|
**한 줄**: 발견 보고 `H-1`~`H-54`를 사용자와 대화형으로 하나씩 결정하고
|
|
`base/` 24개 문서에 반영했다. 그 뒤 `/code-review high`가 7건(그중 셋은 이번
|
|
반영이 만든 회귀), 감사 1라운드가 5건, 2라운드가 7건을 더 잡았다.
|
|
|
|
**결정과 근거의 소스는 `qa-request/pre-implementation-handtrace-round6-followup.md`**
|
|
— 여기서 반복하지 않는다. 이 문서는 **어떻게 진행됐고 무엇이 잘못됐는가**의 기록이다.
|
|
|
|
## 진행 방식 — 사용자 요청
|
|
|
|
*"`pre-implementation-handtrace-round6.md` 에 대해서 같이 하나하나 처리해나가보자.
|
|
질문 모드로 계속 물어보며, 처리할 방법들을 찾거나 택하거나 해보자. 전부 사람이
|
|
직접 읽기엔 양이 많아서 그래. 다만 네 질문과 원 파일은 같이 볼테니, 어디에 원문이
|
|
있는지만 알려줘"*
|
|
|
|
54건을 4·5라운드 같은 문항지로 만들지 않고, **갈래 선택이 필요한 것만 추려
|
|
급한 순서로** 물었다(M2/M3를 막는 것 먼저). 단순 정정은 마지막에 한 번에 묶어
|
|
확인받았다("전부 그대로 반영"). 답변마다 원문 줄 번호를 같이 줬다.
|
|
|
|
## 사용자가 제 선택지를 뒤집은 자리들 — 이번 세션의 핵심
|
|
|
|
에이전트가 낸 갈래가 **틀렸거나 부족했던** 경우가 여럿이고, 그게 결과를 크게
|
|
바꿨다. 기록해둘 가치가 있는 것만:
|
|
|
|
### `H-1` — 제 세 갈래가 전부 차선이었다
|
|
|
|
`:List`의 `keyIndex`가 사이클 도중 stale이라는 발견에 (1) 라이브 인덱스 맵
|
|
(2) `indexOfRaw`를 기본 경로로 (3) reconcile 2패스를 냈는데, 사용자가 **역제안**:
|
|
|
|
> *"raw* 가 층위를 알아야할 이유를 모르겠는 상태. 그냥 k->realElem 을 list 에선
|
|
> 저장하고, raw 는 realElem -> index 를 indexOf 로 하거나, 보통 읽기가 더
|
|
> 빈번하므로 realElem->index 해시맵을 만들어주고, index 밀고 당기는 동작에서
|
|
> 이걸 같이 업데이트해주는 편이 나아보이기도. quad-base 의 현 모양에서 더 확장
|
|
> 될 땐, index 를 알아야하게 되는 경우가 많아질것이라, 선제 처리로 해결하는게
|
|
> 맞아보이는데, 어떻게 생각하는지?"*
|
|
|
|
제 1번 안은 `raw*`가 `:List`의 클로저를 알아야 해서 층이 섞이는 게 단점이었는데,
|
|
**맵을 `_elements`와 같은 층에 두면 그 단점 자체가 없어진다**는 게 요지였다.
|
|
채택. 부수로 `keyIndex`가 단순 키 집합으로 강등되며 `H-16`도 같이 사라졌다.
|
|
|
|
### `H-2` — "offsetAt으로 구하라"가 초기 경로에서 안 맞았고, 그걸 되물었다
|
|
|
|
사용자 답:
|
|
> *"offsetAt 으로 구함 -> 기본적으로 activation 자체는 순차로 일어나므로 length
|
|
> 자체는 확정되는게 맞을것임."*
|
|
|
|
확인해보니 **최초 population에서만 안 맞았다** — `materializeSlotTree`가
|
|
`_mounted = false`인 채 `activateList`를 부르고 `rawAdd`가 부기까지 얼리리턴해서
|
|
`bk.lengthList`가 비어 있었다. 그걸 보고했더니 사용자가 되물었다:
|
|
|
|
> *"1. 안이 맞긴 한데, length 자체는 설정하지 않아? offset 을 넘겨주는건
|
|
> updateFn 기준으로 부기이지, 물리 마운트와는 무관해서 말이야. … 부기 확정에서
|
|
> length 를 확정해도 되는거 아님?"*
|
|
|
|
맞는 지적이었다. **`_mounted`가 "물리 인스턴스 유무"만 뜻하도록 좁히고 부기는
|
|
실체화 시점부터 항상** 하는 것으로 정리됐고, `slot._physicalTarget`이 신설됐다.
|
|
|
|
### `H-40` — 제가 낸 "브랜드로 판정" 안이 성립 불가였다
|
|
|
|
> *"브랜드를 두는건 못 해, 이제 brand 는 각각 따로 생성되어서 있는지 없는지
|
|
> 보는건 결국 전부 봐야한다는 의미일껄? 난 3. 을 추천해. isInst 같은걸 백엔드
|
|
> 계약에 넣고, quad-roblox 가 채워주는게 맞아보이는데 어떻게 생각해?"*
|
|
|
|
2026-08-21의 `Brand` 재작성(인스턴스 브랜드)을 제가 못 따라간 것. 화이트리스트로
|
|
뒤집되 **base는 `T`를 모른 채 주입 술어만 부르는** 형태로 닫혔다.
|
|
|
|
### `H-33`/`H-49` — 제 중첩 합성안이 창을 안 끝나게 만들었다
|
|
|
|
`Gate` 정책 합성에서 제가 `blocker:Policy(debounceOnEmit)`(블로커가 바깥) 형태를
|
|
제안했는데:
|
|
|
|
> *"맞는 모양 같긴 해. 근데 저러면 unblock 될 때 다시 타이머 셋팅됨. 타이머
|
|
> 끝난건데, 다시 셋팅되잖아. … 사실 debounce 는 emit 핸들 조차 필요 없는거지.
|
|
> 그냥 blocker 를 자신이 가지니까, 껐다 켰다로 emit/block 을 하고(내부 구현을
|
|
> 위임). On/Off 할 시기만 본인이 결정한다는 생각이였는데, 이 구조가 불가한거였음?"*
|
|
|
|
가능했고 더 나았다. 제 트레이스는 "디바운스도 진짜 `emit`을 쥔다"를 전제했는데,
|
|
**디바운스가 emit을 아예 안 쥐면** 순차 호출이 성립한다.
|
|
|
|
### 사용자가 먼저 발견한 것 둘
|
|
|
|
- **`Ref.Callbacks`를 해시맵으로** — `H-7`을 "해제 경로 추가"로 정하자마자
|
|
*"그렇다면 Effect 의 callback 들은 해시맵이 되는게 나아보이는데 [callback/루틴]=true
|
|
그렇지 않아?"* 로 자료구조까지 바꿨다. 부수로 `ref-plan.md`의 `#t` border 실측
|
|
항목이 폐기됐다.
|
|
- **`Ref` 콜백의 `canExecute`** — `H-11` 재결정 중에 *"그런데, Effect 또한
|
|
canExecute 를 거쳐서 Ref 의 Callback 이 실제 Effect 의 Callback 을 실행할 지
|
|
결정해야하는데, 그것도 되어있는지 봐줘요"*. 확인해보니 없었고, 오히려 제가
|
|
`H-7` 처리 때 "그 대안은 채택하지 않았다"고 적어놨었다. **해제는 누수를,
|
|
게이팅은 발화를** 막는 별개 역할이라 둘 다 필요했다.
|
|
|
|
## 반영 후 세 번의 재검토에서 나온 것 — 실패 기록
|
|
|
|
### `/code-review high` — 7건, 그중 **셋이 이번 반영이 만든 회귀**
|
|
|
|
`conventions.md`가 *"`/code-review`는 감사자를 대체하지 않는다"*고 적어둔 그대로였다.
|
|
셋 다 **코퍼스 정합성 각도로는 구조적으로 안 보이는** 종류였다:
|
|
|
|
1. **`rawAdd`가 미실체화 가드를 통째로 잃음** — 상태가 셋이 됐다고 산문에 쓰고
|
|
코드엔 `_mounted` 경계만 남겼다. `Slot { frameA }` 생성자가 즉시 크래시.
|
|
2. **`rawRemove`가 마운트 전 창에서 아무도 안 죽임** — `nativeRemove`가 곧
|
|
파괴였는데 `_mounted`로 가리기만 하고 `else`를 비워뒀다. 영구 누수.
|
|
3. **`_listed` 분기가 포탈 재마운트를 깸** — 제가 "`:List`와 CRUD가
|
|
상호배타이니 안전"이라 말하고 승인받았는데 **재마운트 경로를 안 봤다.**
|
|
`activateList`가 멱등 가드로 즉시 리턴하는 경우를 놓친 것.
|
|
|
|
그리고 `H-11`의 두 결정이 **서로 모순**이라는 지적(4번)이 나와 재결정했다 —
|
|
*"`EffectHandle`이 자기 `bindLifetime` 직후에 건다"*는 **그 호출부가 실재하지
|
|
않았다.** 사용자 판단으로 `bindLifetime`/`unbindLifetime`이 직접 처리하는 것으로
|
|
바뀌었다:
|
|
|
|
> *"Destroying 자체가 엔진이 아는 요소이기 때문에, 엔진이 처리하는 곳에 두긴
|
|
> 해야합니다. 옵져버는 바로 생성되기 때문에, bind 상 옵져버 목록을 가져와 자신이
|
|
> 재귀하고, bindLifetime 이 처리하는게 나아보입니다."*
|
|
|
|
제가 반대 근거로 인용했던 *"게이트는 값 타입을 안 가린다"*는 **인용 자체가
|
|
틀렸다** — `source-state-plan.md`의 그 절은 `canBound` 판정이 두 진입점에서
|
|
같다는 얘기지 부수 배선 얘기가 아니었다.
|
|
|
|
### 감사 루프 — 9라운드, 34건 (라운드별 5→7→2→2→3→9→2→4→0)
|
|
|
|
라운드별 각도와 발견 목록은 **followup의 E절이 소스**. 여기엔 교훈만 적는다.
|
|
|
|
**2라운드**: **`.claude/README.md`를 한 줄도 안 고쳤다.** `base/` 19개 파일
|
|
1300줄을 바꿔놓고 색인을 안 건드렸으니 체크리스트 6번을 통째로 빠뜨린 것.
|
|
|
|
**6라운드가 9건으로 가장 많이 잡았고, 그게 각도 전환의 증거다.** 앞선 다섯
|
|
라운드는 전부 "문서가 서로 맞는가"를 물었는데, 6라운드만 **"이 체크박스로
|
|
코드를 짜면 무엇이 나오는가"**를 물었다. 그러자 `ROADMAP.md`의 미완료 항목이
|
|
대거 stale인 게 드러났다 — 폐기된 `pos` 공식이 "확정"으로 남아 있고, 접두합
|
|
캐시 무효화 계약이 통째로 없고, `bindLifetime`이 "둘만 한다"고 적혀 `H-11`과
|
|
직접 모순이었다.
|
|
|
|
**반복된 실패 패턴은 하나 — "N개 중 일부만 고쳤다".** 1라운드(배너를 달고 그
|
|
배너가 부정하는 문장을 안 고침), 2라운드(README를 통째로 안 고침),
|
|
7·8라운드(그 README를 고칠 때 11행 중 6행만). 8라운드를 아예 **완결성 축**으로
|
|
잡은 게 그래서고 4건이 더 나왔다. **세 번 다 `conventions.md`의 핸드오버
|
|
체크리스트가 명시적으로 경고하는 항목이었다**(2번·6번) — 규율이 없어서가
|
|
아니라 규율을 지켰는지 스스로 확인하지 않아서 생긴 실패다.
|
|
|
|
**감사에서 파생된 새 결정 셋**도 `base/`에 반영했다 — 주입 op
|
|
`onDestroying(inst, fn)` 신설(`_bindDestroying` 의사코드를 쓰다 드러났다),
|
|
`EffectHandle`의 필드 다섯, `quad-types`의 `Quad` 갱신을 M3/M6/M7/M8/M10에도
|
|
항목화(확정은 "마일스톤마다"였는데 M2에만 있었다).
|
|
|
|
## 남은 것
|
|
|
|
- **백로그 1건**: `Fallback`/`Traceback` 중 생성된 부분 트리 회수(`H-26`).
|
|
그 둘이 슈가라 구현 시점에 같이 다룬다.
|
|
- **M2 착수를 막는 설계 항목은 없다.** 남은 건 `question.md` 2번(M2↔M3 양방향
|
|
의존, 마일스톤 순서)뿐이다.
|