quad/.claude/luau-test/STATUS.md
qwreey 93f548a2af
docs(type): 0-Y 해소 — 재귀 제네릭 반환은 Luau 상위 한계로 확정, typing-limits.md 신설
44개 스파이크로 question.md 0-Y를 재실측한 결과, 여섯 번째 세션의
"콜백이 raw 값을 받으면 완전 클린" 판정이 틀렸음이 드러남 — 그건 진단
0건만 확인한 것이었고, luau-analyze --annotate로 열어보니 반환 타입이
Unifiable<Error>로 조용히 새고 있었음(틀린 대입도 안 잡힘).

진짜 원인은 콜백 계약이 아니라 Compute가 State<U>(자기 이름을 다른 타입
인자로 감싼 타입)를 반환한다는 것 자체 — RFC relax-recursive-type-restriction이
Promise<T>.andThen으로 예시 든 바로 그 패턴. 사용자 확정: quad가 타입을
비틀 일이 아니라 상위 Luau의 현 한계이고, RFC/이슈 수혜를 받을 때 해결될
일이라 당장 할 수 있는 바 없음.

- base/typing-limits.md 신설 — 흩어져 있던 타입 한계 5건 통합, 대전제
  "Luau 한계를 우회하려 타입/API를 비틀지 않는다", 새 API 설계 체크리스트
- audit/type-recursion-issue/ 신설 — REPORT.md + spikes 44개(audit 폴더에
  스크립트를 같이 둔 첫 예외, 판정 재현에 개별 실행이 필요해서)
- 0-Y 해소 전파: question.md(최우선 2건→1건) / archive / base 5개 /
  research 2개 / 인덱스 4개 / luau-test(08을 done/으로, review-required 비움)
- audit/luau-test-first-run-2026-08-13.md: 판정이 뒤집힌 당사자라 배너뿐
  아니라 본문 표·문단·결론까지 전수 수정
- HUMAN_TODO 6번 신설: luau-lsp 기본이 옛 솔버라 CLI와 진단이 다름

교훈: luau-analyze 진단 0건은 타입 해소를 뜻하지 않음 — 타입 스파이크는
--annotate로 실제 추론 타입을 확인하고 음성 대조군을 같이 둘 것.

doc-check.py ERROR 0 유지(WARN 59건, 변경 전과 동일).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KckSawrsJSJmDBcSojJPxZ
2026-08-13 22:08:51 +09:00

7.9 KiB

스파이크 상태판 — 폴더가 곧 상태

마지막 갱신: 2026-08-13 열세 번째 세션(08 해소 → done/, review-required/ 비워짐). 첫 실측은 여섯 번째 세션 — 상세 결과는 .claude/audit/luau-test-first-run-2026-08-13.md. 실행법: luau <파일> (런타임) / luau-analyze <파일> (타입 전용).

[2026-08-13 열세 번째 세션] review-required/가 비었습니다 — 마지막 한 건이던 08이 해소돼 done/으로 갔습니다. 지금 남은 건 에이전트가 처리할 일(rewrite-required/)과 Studio가 필요한 일(not-run/)뿐입니다.

폴더 개수 누가 처리
review-required/ 설계가 걸림 — 사람 결정 필요 0 사용자
rewrite-required/ 스파이크 코드가 깨짐(설계 문제 아님) 3 에이전트
not-run/ 이 환경에서 못 돌림(Studio 전용) 1(+헬퍼 1) 사용자 or MCP 연결 후 에이전트
done/ 통과 or 판정 끝, 더 할 일 없음 16

폴더를 옮기는 게 곧 상태 갱신 — 스파이크를 고치거나 돌렸으면 파일을 해당 폴더로 git mv하고 아래 표의 줄도 같이 옮길 것. 파일별 "무엇을 왜 검증하는가"는 README.md가 담당(이 파일은 상태만).


review-required/ — 사람 결정 필요 (0건, 비어 있음)

[2026-08-13 열세 번째 세션] 마지막 한 건이 해소됐습니다. 08-type-source-satisfies-state.luau가 남겨뒀던 잔여 케이스(State<T>가 자기 자신을 다른 타입 인자로 재귀 참조하면 막힘)는 Luau의 현 한계로 확정되어 quad가 설계로 풀 대상이 아님이 정해졌고(구 question.md 0-Y 해소), 스파이크는 done/으로 이동했습니다. 당시 검토됐던 "구울 때 인라이닝" 방향은 채택 안 함.

  • 지금 유효한 규약: base/typing-limits.md
  • 실측 근거 전문(스파이크 44개 포함): audit/type-recursion-issue/

15도 같은 계약을 다루지만 스파이크 자체가 파싱 실패rewrite-required/에 그대로 둠 — 재작성 대상이지 사람 결정 대상이 아님(계약 자체는 위에서 이미 확정됨).

🟠 rewrite-required/ — 스파이크가 깨짐, 설계 문제 아님 (3건)

파일 상태 무엇을 고쳐야 하나
13-type-ref-preref-subtype.luau 타입 A섹션 통과 / 런타임 B섹션 실행 불가 B가 A의 더미 스텁(fakePreRef = nil)에 막혀 도달 못 함 — 두 섹션을 파일로 분리
15-type-compute-trailing-deps-typepack.luau 파싱 실패(SyntaxError) 음성 대조군의 타입 표기가 TypeError가 아니라 SyntaxError로 걸려 파일 전체가 아무것도 검증 못 함 — 대조군을 별도 파일/블록으로 격리
16-type-store-key-typefunction.luau 실패 types.newfunction 시그니처가 설치된 버전의 실제 API와 안 맞음 — 실제 API 재확인 후 재시도

not-run/ — 이 환경에서 못 돌림

파일 이유
10-roblox-studio-checks.server.luau Studio 전용(luau CLI로 못 돌림). A 섹션 앞부분만 사용자 자작 스크립트로 실측 — audit/gcconn-trick-verification.md. A-1/A-2(canBound 게이트)/B/C는 여전히 미확인
gc-trigger-helper.server.luau 스파이크가 아니라 헬퍼 — Studio에 collectgarbage()가 없어서 GC를 강제 트리거하는 기법. 10을 돌릴 때 같이 씀

done/ — 통과 or 판정 끝 (16건)

런타임 12개 전원 통과(crash 0 / FAIL 0):

파일 확인된 것
01-two-pass-array-hash-order 배열 파트 전체 → 해시 파트 순. Dispatch.drive 두 패스 계약과 PreRef 호이스팅의 전제
02-none-sentinel-vs-nil-holes nil 소진 시 #t 50→49로 무너짐 / None은 항상 50. 반대로 Ref 콜백 배열은 None 쓰면 죽은 슬롯 1000개 잔존 — 두 배열의 규칙이 서로 반대여야 함이 정량 확인
03-recursive-store-bind-dispatch StoreBind 재귀 재-dispatch, Nonenil 흐름, 무한재귀 없이 종료
04-dispatch-chain-retractFrom 인덱스 기반 체인 + 음성 대조군이 감사 버그를 재현(아래 별도 절)
05-store-state-diamond-propagation 다이아몬드에서 재계산 정확히 1회, invalidate 2번째는 즉시 중단
06-component-boundary-nil-hole-props or None 없으면 앞쪽 nil-hole로 슬롯 소실, 관용구 쓰면 항상 보존
07-relate-weak-table-gc 연쇄 GC 확정(아래 별도 절) — GC-native 아키텍처의 핵심 전제
11-modifier-illegal-value-error Modifier 필드/Source에 핸들러 계층 값 넣으면 즉시 error — 16개 케이스 전원
17-modifier-index-tableclone-chaining 제네릭 __index + table.clone 체이닝, 메타테이블 참조 공유, 형제 분기 무오염
18-relate-mutual-cycle-gc Relate 상호 순환은 실제로 GC 안 됨(아래 별도 절)
19-ownership-refcount-relate-patterns Tag 참조 카운트 / Attribute 점유 체크 / Slot claimOwner vs claimOwnerAt음성 대조군 포함 전원 통과. ⚠️ B 섹션은 question.md 0-Z가 정해지면 다시 손봐야 함(하강 diff 모델에선 그룹↔그룹을 점유 체크만으론 못 잡음)
20-slot-splice-index-arithmetic Splice 산술 11개 경계 케이스 전부 참조 구현과 일치

타입 스파이크 중 판정이 끝나 더 할 일 없는 것:

파일 판정
08-type-source-satisfies-state 핵심 질문(Source⊇State 구조적 서브타이핑) 통과. 잔여 케이스(자기 이름을 다른 인자로 재귀 참조)는 [2026-08-13 13차 세션] Luau 현 한계로 확정 — quad가 풀 대상 아님, base/typing-limits.md 1번
09-type-modifier-overridden-subtype 통과 — 문서가 우려한 FrameModifierGuiObjectModifier 서브타입 깨짐이 그대로 재현, fallback(any)은 정상
12-type-attribute-generic-key-narrowing 지만 설계 영향 없음 — 제네릭 키 narrowing이 안 되는 건 attribute-plan.md가 이미 fallback으로 예비해둔 결과(타입 패밀리가 유일하게 믿을 경로)
14-type-nilable-default-overload ⚠️ 부분 — 의도한 오용은 막지만 정상 nilable 사용례까지 막아 현 스케치로는 채택 불가. 설계 결정은 아직 필요 없음(대안이 이미 UB 경고로 존재)이라 review-required가 아님

특별히 중요한 통과 3건

04 — 직전 감사가 찾은 버그가 음성 대조군으로 재현됨

관측 지점 정상(수정본) 대조군(버그)
최초 마운트 후 체인 깊이 3 1
재발행 후 옛 store 구독 0 (끊김) 1 (안 끊김)
죽은 store를 건드리면 값 유지 STALE로 덮어써짐

chains:SetStronghandler.process 뒤에 두면 하위 retractor가 통째로 유실되고, 결국 버려진 store가 나중에 UI를 덮어쓰는 데까지 감.

07 — GC-native 아키텍처의 핵심 전제

inst 5개만 살린 상태 → 살아남은 payload 5 / 엔트리 5   (기대치 일치)
모든 참조를 놓은 뒤   → 살아남은 payload 0 / 엔트리 0   (기대치 일치)

bindLifetime으로 매달아둔 자원이 inst와 함께 연쇄 소멸함이 확인됨. (이 스파이크는 원래 sanity check만 하고 있어서 이번에 보강한 것 — 파일이 스스로 적어둔 "weak table 엔트리를 셀 방법 없음"이라는 전제가 틀렸음.)

18relate-plan.md의 상호 순환 경고

상호 강참조 순환:        inst=true,  value=true   (GC 못 풂)
한쪽을 weak-value로 낮춤: inst=false, value=false  (풀림)

추측이 아니라 실제로 GC가 안 됨Slot의 두-Relate 수정이 필수 조치였음이 입증.