quad/.claude/audit/handtrace-round10-reference-impl/spikes/t16_gate_resub_refresh.luau
qwreey d2d67c7aeb
qa: 10라운드 광범위 탐사 완료 — 발견 H-150~H-157 (🔴 0 / 🟡 5 / 🟢 3), §4 배치 문항 7건 회신 대기
- 신선한 탐사자(fable) 단일 컨텍스트, 지시서 -round10-brief.md §2 레인 A·C 완료, B 부분, D ALL PASS
- audit/handtrace-round10-reference-impl/: round7 참조 구현을 현재 계약으로 갱신·재실행
  (부수: round7/ref9 _recompute 첫 인자 오류 발견·정정)
- base/·인덱스 레이어는 미변경 — 결정은 사용자 배치 회신 뒤 -round10-followup.md로

Co-authored-by: qwreey <me@qwreey.moe>
Claude-Session: https://claude.ai/code/session_01546hjsYNLSMZdHdPyTZaGb
2026-08-28 01:03:05 +09:00

100 lines
4.9 KiB
Text
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

--!nocheck
-- 레인 A: state:Gate 유보 × Effect 재구독/재바인드 × EpochMap:Refresh / Peek / Update 조합
local q = require("./core10")
local function runs(e) return e._n end
local function mkEffect(...)
local e; e = q.Effect(function(self) self._n = (self._n or 0) + 1 end, ...)
return e
end
print("== (1) 게이트 유보 중 재구독: Refresh가 유보된 변경을 먼저 삼킨다 ==")
local A = q.Source(1)
local b = q.Blocker()
local gated = A:Block(b)
local e = mkEffect(gated)
e:Subscribe()
print("초기 runs", runs(e), "(기대 1)")
b:On()
A:Set(2) -- gated가 유보. Effect엔 아무것도 안 온다
print("블록 중 A:Set → runs", runs(e), "(기대 1) | gated withheld:", next(gated._withheld) ~= nil)
e:Unsubscribe(); e:Subscribe() -- 재구독 꼬리: Refresh()가 A의 새 리비전을 본다 → Rerun
print("재구독 후 runs", runs(e), "(기대 2 — 게이트가 잡고 있는데도 캐치업이 fn을 돌렸다)")
print(" fn이 읽는 gated:Get() =", gated:Get(), "(게이트는 값은 안 가림)")
b:Off() -- 유보 풀림 → batch {A} → Effect Update(A) → 이미 최신 → 접힘
print("Off 후 runs", runs(e), "(기대 2 — 접힘, 중복 없음)")
print("\n== (2) 재바인드(포탈) 캐치업도 게이트를 우회한다 ==")
local A2 = q.Source(1); local b2 = q.Blocker(); local g2 = A2:Block(b2)
local e2 = mkEffect(g2)
local inst = q.newInst("inst")
q.bindLifetime(inst, e2)
b2:On(); A2:Set(2)
q.unbindLifetime(e2)
local inst2 = q.newInst("inst2")
q.bindLifetime(inst2, e2)
print("블록 중 언바인드→재바인드 후 runs", runs(e2), "(기대 2 — 캐치업이 게이트를 우회)")
b2:Off()
print("Off 후 runs", runs(e2), "(기대 2)")
print("\n== (3) emit(false) 버리기: Effect가 캐치업으로 이미 돌았다면? ==")
local A3 = q.Source(1); local g3 = A3:Gate(function(emit)
return function() end -- 항상 유보(정책이 emit을 안 부름)
end)
local flush3 = nil
-- 정책 밖에서 emit 핸들을 얻으려고 Gate를 다시 감싼다
local A3b = q.Source(1); local disc
local g3b = A3b:Gate(function(emit) disc = emit; return function() end end)
local e3 = mkEffect(g3b); e3:Subscribe()
A3b:Set(2)
e3:Unsubscribe(); e3:Subscribe()
print("유보 중 재구독 runs", runs(e3), "(기대 2)")
print("emit(false) 반환:", disc(false), "(버릴 게 있었으니 true)")
A3b:Set(3)
print("버린 뒤 새 Set → runs", runs(e3), "(기대 2 — 여전히 유보 중)")
print("emit() 반환:", disc(), "→ runs", runs(e3), "(기대 3)")
print("\n== (4) 다이아몬드: A→gated, A→plain, Effect(fn, gated, plain) — 비게이트 경로가 게이트를 새게 한다 ==")
local A4 = q.Source(1); local b4 = q.Blocker()
local g4 = A4:Block(b4)
local p4 = A4:Compute(function(s) return s:Get() end)
local e4 = mkEffect(g4, p4); e4:Subscribe()
b4:On(); A4:Set(2)
print("블록 중 A:Set → runs", runs(e4), "(기대 2 — plain 경로가 Effect를 깨웠다; 게이트는 무의미)")
b4:Off()
print("Off 후 runs", runs(e4), "(기대 2 — 접힘)")
print("\n== (5) 게이트 위의 다이아몬드: A→B→G, A→C→G, 유보 중 두 번 도착 → 정책 두 번, 집합 하나 ==")
local A5 = q.Source(1)
local B5 = A5:Compute(function(s) return s:Get() end)
local C5 = A5:Compute(function(s) return s:Get() end)
local M5 = B5:With(C5) -- fan-in
local policyCalls = 0
local g5 = M5:Gate(function(emit) return function() policyCalls += 1 end end)
local o5 = g5:Observer(function() end); o5:Subscribe()
A5:Set(2)
local cnt = 0; for _ in pairs(g5._withheld) do cnt += 1 end
print("A:Set 한 번 → 정책 호출", policyCalls, "(다이아몬드가 M5에서 접히면 1) | withheld 크기", cnt)
print("\n== (6) 게이트 → 게이트 (unfold) 유보 후 각각 풀기 ==")
local A6 = q.Source(1); local b6a, b6b = q.Blocker(), q.Blocker()
local g6a = A6:Block(b6a); local g6b = g6a:Block(b6b)
local fires = 0
local o6 = g6b:Observer(function(_, _, from) if from ~= nil then fires += 1 end end); o6:Subscribe()
b6a:On(); b6b:On(); A6:Set(2); A6:Set(3)
b6a:Off()
print("안쪽 게이트 풀림 → 바깥 withheld에 합류, 발화", fires, "(기대 0)")
b6b:Off()
print("바깥 풀림 → 발화", fires, "(기대 1)")
A6:Set(4)
print("둘 다 열린 뒤 Set → 발화", fires, "(기대 2)")
print("\n== (7) 게이트 유보 중 하류 Get()으로 앞당겨 읽기 → 풀릴 때 규칙 2(통지만) ==")
local A7 = q.Source(1); local b7 = q.Blocker(); local g7 = A7:Block(b7)
local d7 = g7:Compute(function(s) return s:Get() * 10 end)
local n7 = 0
local o7 = d7:Observer(function() n7 += 1 end); o7:Subscribe()
b7:On(); A7:Set(2)
print("유보 중 d7:Get() =", d7:Get(), "(기대 20 — 값은 안 가림) recomputes", d7.recomputes)
local rc = d7.recomputes
b7:Off()
print("Off 후 Observer 발화", n7, "(기대 1 — 통지는 온다) | 재계산 추가", d7.recomputes - rc, "(기대 0 — 규칙 2)")