# 한계 — 이 프로젝트가 못 하는 것, 검증 안 한 것, 근사한 것 README가 인상적으로 들린다면, 그 별표는 전부 여기 붙어 있습니다. 한 줄 요약: **이 초파리는 게임을 잘 하지 못합니다.** 목표를 향해 가지 못합니다. 그런데도 쓸모가 있는 이유는, QA 퍼저에게 필요한 것이 목표 추적이 아니라 지치지 않는 탐색이기 때문입니다. --- ## 1. 커넥톰은 초파리를 목적지로 데려가지 못합니다 가장 중요한 한계입니다. 측정 결과를 그대로 적습니다. 게임 중에 조향 신호와 "빛이 있는 방향" 사이의 상관계수를 8개 시드로 쟀습니다. | 디코더 방식 | 평균 상관계수 | |---|---| | 좌우 비율 `(r-l)/(r+l)` | 0.046 ± 0.272 | | 적응형 기준선 (감각 적응) | 0.077 | | 정류 임계 + tanh (Xenova 방식) | −0.042 ± 0.111 | | 이산 saccade (200ms 적분) | 0.046 ± 0.272 | **전부 0입니다.** 방향을 추적하지 못합니다. 정적인 조건에서는 좌우를 구분합니다. 한쪽 눈만 밝게 하고 0.9초 기다리면 조향 신호가 분리도 0.95로 갈립니다. 그러나 닫힌 루프에서는 그 신호가 초파리 머리가 도는 속도를 따라가지 못합니다. 시각→하행뉴런 경로의 지연과 잡음이 원인으로 보이나, 확정하지 못했습니다. ### 우리만 그런 것이 아닙니다 이 분야의 다른 구현을 열어봤습니다. - **`abgunaydin/webgpu-fly`** — `LIMITATIONS.md`에 *"the connectome does not walk the fly"* 라고 직접 씁니다. 2,030만 연결이 몸에 닿는 건 틱당 스칼라 하나와 회전 편향 하나이고, 그 둘이 손으로 짠 `sin(t × 10Hz)` 삼각보행을 스케일합니다. - **`Xenova/fruit-fly-simulation`** — README에 *"The movements are illustrative, not validated predictions of fly behavior"*. 디코더 첫 줄 주석이 "Engineered rate decoder"입니다. - **`snedea/flybrain`** — README는 *"Behavior is not scripted"* 라고 하지만, 조향 코드는 뇌 기여를 `Math.max(-0.05, Math.min(0.05, newDir))` 로 클램프하고 실제 방향은 `atan2` 로 직접 계산합니다. 주석에도 *"Steering is primarily handled by behavioral biases"* 라고 적혀 있습니다. 클램프 이유는 *"to prevent worker noise from causing spinning"* 입니다. - **`nftechie/doomfly`** — 이 분야에서 가장 멀리 간 시도입니다. MaleCNS v1.0(166,700뉴런, 2,560만 연결)에 도파민 가소성까지 붙였습니다. 손상 시 PPL101 도파민 세포 2개에 200ms 혐오 자극을 주고 KC→MBON11 연결 4,184개를 가소성 규칙으로 바꿉니다. 그런데 README 첫 줄이 이렇습니다: > **Status: live experimental training, not demonstrated learned survival.** > The current v6 candidate failed its visual, conditioning and survival validation gates. 조향은 `turn = clip((rate(DNp20,R) - rate(DNp20,L)) * .12, -6, 6)` 입니다. 그들 스스로 *"engineered controller assignments, not established natural motor functions"* 라고 적어놨습니다. **우리 모델에서 DNp20 을 직접 재봤습니다.** | 읽기 방식 | 좌우 분리도 | |---|---| | DNp20 R−L (doomfly 채택) | **0.18 Hz** | | DNa02 R−L | 7.45 Hz | | 이 프로젝트의 통계 채널 | **0.889** (정규화) | DNp20 은 좌우를 거의 구분하지 못합니다. 개체당 좌우 각 1개뿐이라 표본이 너무 작습니다. **아무도 못 했습니다.** 강화학습을 붙인 쪽도 자기 검증을 통과하지 못했습니다. 우리는 그래서 조향을 보정하지 않기로 했습니다. 초파리가 헤매는 모습이 지금 기술의 실제 모습입니다. ## 1.5 왜 못 하는가 — 신호가 T4 까지 살아서 가지 못한다 "시각 정보를 더 제대로 주면 되지 않나"를 끝까지 파봤습니다. 결론은 **입력 문제가 아니다** 입니다. ### 실험 1 — 밝기 대신 움직임을 줬다 초파리 시각계는 밝기가 아니라 시간 변화를 읽습니다. 그래서 ON/OFF 시간 대비를 만들어 해부학적으로 올바른 자리에 넣었습니다. L1(ON 경로)과 L2(OFF 경로)입니다. ``` L1 125Hz → Mi1 0.08Hz Tm3 0.00Hz Mi4 0.00Hz Mi9 0.00Hz ON 경로 전멸 L2 125Hz → Tm1 26.0Hz Tm2 15.5Hz Tm4 21.0Hz OFF 경로 정상 ``` > 아래 두 문단은 **틀린 설명**입니다. 왜 틀렸는지는 이 절 끝의 > [정정](#정정--시간-상수를-넣어-봤고-아무-일도-없었습니다)에 있습니다. 지우지 않고 남겨둡니다. **OFF 경로는 살고 ON 경로는 죽습니다.** 초파리의 ON 경로는 *억제를 통한 부호 반전*으로 작동하기 때문입니다. L1 은 글루탐산성 억제 시냅스로 Mi1 을 누르고, Mi1 이 다시 반전시켜 ON 신호를 만듭니다. 우리 모델에는 **기저 발화가 없어서**, 조용한 뉴런을 억제해 봐야 아무 일도 일어나지 않습니다. ON 입력을 받는 T4 는 그래서 영영 울지 않습니다. ### 실험 2 — 기저 활동을 넣었다 모든 뉴런에 기저 전류를 넣어 억제가 작동할 바닥을 만들어 줬습니다. HS(수평 광류 적분 뉴런)가 0 → 6.7Hz 로 깨어났습니다. 그러나 **방향 선택성은 0** 이었습니다. | 기저 | 오른쪽 흐름 | 왼쪽 흐름 | 방향 분리 | |---|---|---|---| | 5.5 | 0.00 | 0.00 | 0.00 Hz | | 6.0 | 0.00 | 0.00 | 0.00 Hz | | 6.5 | 0.26 | 0.00 | 0.26 Hz | ### 실험 3 — 가지치기를 풀었다 시각 경로의 연결을 2배(270만 → 590만, 시냅스 62.7% → 78.4%)로 늘렸습니다. T4 는 여전히 **0.0Hz** 였습니다. ### 그래서 진짜 이유 — ~~시간 상수~~ (2026-09-18 철회) > **철회합니다.** 아래 설명은 2026-09-18 에 배선을 직접 세어 보고 틀린 것으로 확인했습니다. > 지운 이유를 남겨두기 위해 원문을 그대로 두고 아래에 정정을 붙입니다. ~~**T4/T5 의 방향 선택성은 뉴런마다 다른 시간 특성에서 나옵니다.** Mi9 는 느린 OFF, Mi1/Tm3 는 빠른 ON, Mi4 는 지연된 억제 — 이 시간차를 비교해 움직임의 방향을 계산합니다. 우리 모델은 모든 뉴런에 똑같이 τ_m = 20ms, τ_syn = 5ms 를 씁니다. 커넥톰은 배선을 주지만 동역학은 주지 않습니다.~~ ### 정정 — 시간 상수를 넣어 봤고, 아무 일도 없었습니다 `docs/lif.js` 에 뉴런별 막 시상수를 넣었습니다(`setTau`). 그리고 막대를 시야에 지나가게 하면서 다섯 가지 배합으로 쟀습니다. | 시상수 배합 | L1 | L2 | **T4** | T5 | |---|---|---|---|---| | 균일 20ms | 34Hz | 33Hz | **0.00Hz** | 0.00Hz | | L1 빠름 / L2 느림 | 34 | 33 | **0.00** | 0.00 | | T4·T5 만 40ms | 34 | 33 | **0.00** | 0.00 | | L1 80 / L2 8 / T4·5 30 | 34 | 33 | **0.00** | 0.00 | | 전부 느리게 | 34 | 33 | **0.00** | 0.00 | 시간 상수는 T4 를 깨우지 못합니다. 가설이 틀렸습니다. ### 진짜 이유 — 신호가 T4 에 닿기 전에 사라집니다 배선을 직접 셌습니다(`wiring.mjs`). ``` T4: 들어오는 연결 47,625개 · 시냅스 446,789 L1 0 L2 0 광수용체하류 0 수질 24,045 기타 23,580 ``` **T4 는 L1 에서도 L2 에서도 연결을 하나도 받지 않습니다.** 위에 적었던 "L1 → T4 가 부호 반전에 기대므로 죽는다"는 설명은 존재하지도 않는 경로를 설명하고 있었습니다. T4 는 수질(medulla)을 한 단계 거쳐서 입력을 받습니다. 그래서 단계별로 발화율을 따라가 봤습니다(`chain.mjs`). ``` 시각신경 31Hz → L1·L2 21Hz → 수질 전체 2.6Hz → T4 직접 입력원 0.56Hz → T4 0.00Hz ``` 단계마다 대략 10배씩 줄어듭니다. 문제는 타이밍이 아니라 **감쇠**입니다. 시냅스 하나의 가중치(`W_SYN = 0.44`)로는 한 뉴런이 문턱(−52 → −45mV)을 넘기려면 동시에 우는 입력이 아주 많아야 하는데, 단계를 거칠수록 그 수가 모자라집니다. ### 이득을 올리면 T4 는 웁니다. 대신 나머지가 망가집니다 `gain.mjs` 로 `W_SYN` 을 쓸어 봤습니다. | W_SYN | T4 | L1 | 하행뉴런 | |---|---|---|---| | 0.44 (현재) | 0.00Hz | 33Hz | 12Hz | | 0.9 | 0.04 | — | — | | 1.5 | 0.43 | — | — | | 2.5 | 1.27 | — | — | | 4.0 | 2.84 | — | — | | 6.0 | **4.32** | **70** | **58** | T4 를 깨우는 이득은 L1 을 33 → 70Hz 로, 하행뉴런을 12 → 58Hz 로 같이 부풀립니다. 초파리의 실제 발화율은 대부분 수 Hz~수십 Hz 대입니다. 즉 이건 생리적으로 맞춰 넣는 게 아니라 **전체를 과흥분시켜 T4 숫자만 0 이 아니게 만드는 것**입니다. 그래서 남은 질문은 하나뿐이었습니다 — **T4 가 울면 실제로 더 잘 도는가.** 의견으로 답할 수 없어서 같은 벤치를 `W_SYN` 만 바꿔 돌렸습니다. 초파리 정책, 3회차, 회차당 24,000프레임, 나머지 조건 전부 동일: | W_SYN | T4 발화 | **탐색률** | 범위 | 증상 종류 | |---|---|---|---|---| | **0.44** (현재) | 0.00Hz | **42%** | 35–50 | 2.7 | | 1.5 | 0.43Hz | **34%** | 31–39 | 3.0 | | 4.0 | 2.84Hz | **25%** | 17–38 | 2.3 | **T4 를 깨울수록 성능이 나빠집니다.** 단조롭게 떨어집니다. 이유는 짐작할 수 있습니다. 이득을 올리면 T4 만 깨는 것이 아니라 하행뉴런까지 12 → 58Hz 로 같이 부풀고, 그러면 좌우 채널이 둘 다 포화해 **불균형이 사라집니다.** 조향 신호는 좌우 비율이라 양쪽이 같이 커지면 0 에 가까워집니다. 그래서 `W_SYN = 0.44` 를 그대로 둡니다. T4 가 우는 그림은 못 싣지만, 숫자가 예뻐지는 것과 성능이 좋아지는 것은 다른 일이고 우리는 성능만 싣습니다. ### 여전히 맞는 결론 커넥톰은 배선을 줍니다. 이 배선을 균일 LIF 로 돌리면 시각 운동 경로가 작동하지 않습니다. 다만 그 이유는 시간 상수가 아니라, 우리가 고른 시냅스 가중치 하나로는 5단계 경로를 신호가 살아서 통과하지 못하기 때문입니다. 층별로 이득을 맞추는 것은 곧 **커넥톰이 주지 않은 값을 우리가 넣는 것**이고, 그러면 그건 더 이상 "커넥톰만으로"가 아닙니다. ## 1.7 온라인 학습을 붙였습니다 — 그리고 무엇이 좋아졌는지만 씁니다 지금까지 초파리 정책의 조향은 **손으로 짠 판독**이었습니다. 좌/우 채널의 불균형을 재서 방향으로 바꾸는 식입니다. 그 판독이 이 게임에 맞는다는 보장은 어디에도 없습니다. 커넥톰은 배선을 줄 뿐, 어느 배선이 이 판에서 쓸모 있는지는 말해주지 않습니다. 그래서 판독을 고정하지 않고 판에서 밀어봤습니다. 조향 채널 하행뉴런 40개의 발화율을 특징으로 쓰고, 그 위에 선형 가중치를 얹습니다. 매 프레임 작은 섭동을 섞어 보고, 그 섭동이 **처음 보는 장면**으로 데려갔으면 그 방향으로 가중치를 밉니다. **뇌는 건드리지 않습니다.** 시냅스도 커넥톰도 그대로입니다. 배우는 것은 판독 층뿐입니다. ### 보상을 좌표가 아니라 장면으로 한 이유 두 가지입니다. 첫째, 이 프레임워크의 요지는 "남의 게임에 붙는다"인데 남의 게임에서는 주인공 좌표를 모릅니다. 픽셀밖에 없습니다. 둘째가 더 중요한데 — 우리가 표에 싣는 지표(탐색률)를 그대로 보상으로 주면 **"학습이 이겼다"가 동어반복**이 됩니다. 장면 신기성은 탐색률의 대리지 탐색률이 아닙니다. ### 재는 방법 — 짝지은 대조군 학습 정책에는 탐색용 섭동 잡음이 들어갑니다. 이 판은 잡음만으로도 61%가 나오는 판이라, 그냥 돌려서 나온 숫자는 학습의 몫인지 잡음의 몫인지 구분되지 않습니다. 그래서 `learn0` 을 둡니다 — **같은 시드, 같은 판 순서, 같은 섭동열, 학습률만 0.** 회차마다 둘을 짝지어 돌리고 회차별 차이를 봅니다. 판 난이도 편차가 양쪽에서 상쇄됩니다. ### 결과 12회차, 회차당 24,000프레임: | | 탐색률 | |---|---| | 학습함 (`learn`) | **40%** | | 대조군 (`learn0`, 학습률 0) | 27% | | **차이** | **+13.1%p** | 표준오차 4.2%p, **t = 3.15** (자유도 11, 양측 95% 임계값 2.201). 12회차 중 10회차가 양수이고, 가장 튀는 회차를 빼도 +10.8%p입니다. **학습은 실재합니다.** 6회차만 돌렸을 때는 t = 1.99 로 서지 않았습니다. 회차를 늘려서 갈랐습니다. ### 그러나 이깁니다, 자기 대조군만 절대 수준을 같이 봐야 합니다: ``` 학습 40% ← 자기 대조군(27%)은 확실히 이긴다 초파리 단독 42% ← 학습이 여전히 못 넘는다 평활 난수 61% ← 아무도 못 넘는다 ``` 학습이 탐색하려고 넣는 섭동 잡음의 비용이, 배운 판독이 벌어오는 것보다 큽니다. 그래서 손으로 짠 판독을 그대로 쓰는 초파리 단독에도 못 미칩니다. **"온라인 학습을 붙였고 통계적으로 작동한다"와 "그래서 더 잘한다"는 다른 문장입니다.** 앞의 것만 참입니다. ### 판정 기준에 있었던 결함 처음에 유의성을 `|평균| > 2 × 표준오차` 로 판정했습니다. 회차가 적을 때 이건 틀립니다 — 자유도 5의 양측 95% t 임계값은 2.0이 아니라 **2.571** 입니다. 이 규칙 때문에 t = 2.00 인 결과가 한 번 "잡음보다 큼"으로 잘못 찍혔습니다. 자유도별 t 임계값을 쓰도록 고쳤고, 가장 튀는 회차를 뺀 평균도 같이 찍게 했습니다. ## 1.8 목표를 못 찾는 것과, 제자리에서 도는 것은 다르다 항법이 안 되는 것은 위에 적은 이유로 어쩔 수 없습니다. 그러나 **제자리에서 빙빙 도는 것**은 별개의 문제였고, 그건 우리 잘못이었습니다. 조향 신호에 남아 있던 고정 편향과 포화 때문에 초파리가 구석에서 계속 같은 방향으로만 돌았습니다. 적응 시간과 이득을 **탐색 범위를 기준으로** 실측해 다시 정했습니다. > **이 절은 절반만 맞았습니다 (2026-09-18 추가).** 아래 표는 실제로 개선된 값이지만, > "고쳤다"고 쓴 것은 일렀습니다. 편향이 0.273 → 0.007 로 줄었다고 적어놓고도 > 실제로는 **+0.044** 가 남아 있었고, 그것만으로 1500프레임마다 0.9바퀴를 돌았습니다. > 사용자가 페이지에서 "빙빙 돈다"고 지적해서 다시 쟀습니다. 아래 "### 다시 쟀습니다" 로. | | 이전 | 이후 | |---|---|---| | 조향 평균 | 0.273 (오른쪽 치우침) | **0.007** | | 포화 비율 | 20% | **0%** | | 총 회전량 | 8.3바퀴 | **1.9바퀴** | | 탐색 범위 x | 20~179 | **30~319** (맵 전체 폭) | | 탐색 범위 y | 20~45 | **22~179** | | 방문한 칸 | 16 | **30** | ### 다시 쟀습니다 — 여전히 돌고 있었습니다 위 표는 한 판을 짧게 본 값이었습니다. 제대로 재니 이렇습니다(1500프레임 4시드): | | 값 | |---|---| | 조향 평균 | **+0.044** (오른쪽 치우침이 남아 있다) | | 조향 크기 \|s\| | 0.14 (포화율 0~4% — 조향 범위를 거의 안 쓴다) | | **선회반경** | **40px** (판이 320×240 이다 — 지름 80px 원을 그린다) | | 순회전 | 0.61바퀴 / 1500프레임 | 숫자가 서로 맞습니다. `1500 × 0.09 × 0.044 = 5.9rad ≈ 0.94바퀴`. **남은 편향 하나로 도는 것이 전부 설명됩니다.** 편향을 지우라고 넣어둔 적응형 기준선이 너무 느렸습니다. 적응을 1/40 → **1/10** 으로 올렸습니다. 상수는 이것 하나만 바꿨습니다. | | 이전 (1/40) | 이후 (1/10) | |---|---|---| | 조향 평균 | 0.044 | **0.008** | | 순회전 | 0.61바퀴 | **0.09바퀴** | | 선회반경 | 40px | **113px** | | **탐색률 (24,000프레임 3회차)** | 42% | **50%** | 증상 종류는 2.7 → 2.0 으로 떨어졌는데, 이건 행동이 나빠진 것이 아닙니다. 덜 끼니 재시작이 줄고, 프레임 예산이 고정이라 판을 42 → 34 개만 봤습니다. **판당 버그 발견율은 0.95 → 0.94 로 같습니다.** > 여기서 방법론 구멍이 하나 드러납니다. **"증상 종류"를 고정 프레임 예산에서 재면 > 잘 도는 정책이 불리하게 나옵니다** — 덜 끼는 만큼 판을 덜 갈기 때문입니다. > 표의 증상 종류 열을 정책 간 직접 비교에 쓰면 안 됩니다. 그리고 짧은 시험을 믿으면 안 된다는 것도 같이 배웠습니다. 1500프레임 4시드에서 방문 칸이 가장 많던 조합(이득 1.0)이 24,000프레임 벤치에서는 **20%로 꼴찌**였습니다. 튜닝은 전체 벤치로만 정합니다. ### 그리고 두 번째 문제 — 끼면 못 빠져나온다 한 판을 길게 돌려도 탐색이 늘지 않았습니다. 750프레임에 12칸, 6000프레임에 8칸. **초파리가 일찍 끼여서 그대로 머물기 때문**입니다. 여기서 쓴 해결책은 뇌를 손대지 않습니다. **끼임·진행불가·정지가 감지되면 그 판을 끝내고 새 판을 시작합니다.** 버그는 그대로 기록되고, 실제 퍼저가 하는 일과 같습니다. 같은 계산량(24,000프레임)으로 3회차 평균을 냈습니다. | | 판수 | 탐색률 | 증상 종류 | 발견 | |---|---|---|---|---| | 6000프레임까지 버틴다 | 5 | 9% | 2.7종 | 10건 | | **끼면 바로 새 판** | **53** | **37%** | **3.0종** | **53건** | **같은 계산량에서 맵을 4배 넓게 두들깁니다.** 발견 건수도 5배지만, 이 도구의 입장상 건수보다 **탐색률**이 근거입니다. ### 시도했다가 버린 것 판마다 다른 지점에서 시작하게 해봤습니다. 실제 퍼저가 초기 상태를 무작위화하듯이요. **더 나빠졌습니다.** 작은 막다른 구역에서 시작하는 경우가 생겨서로 보입니다. 측정하고 폐기했습니다. (이 비교는 중복 집계를 고치기 전에 잰 것이라 숫자는 싣지 않습니다.) QA 퍼저에게 중요한 것은 목표 도달이 아니라 **얼마나 넓게 두들기느냐**입니다. 목표를 못 찾는 것은 모델의 한계지만, 넓게 돌아다니지 못한 것은 고칠 수 있는 문제였습니다. ## 1.9 초파리는 난수에게 진다 이 도구의 본체는 QA 프레임워크입니다. 화면을 읽고, 입력을 넣고, 증상을 찾고, 입력열을 저장해 재현합니다. **무엇이 입력을 만드는지는 갈아 끼울 수 있습니다.** 그래서 같은 조건에서 쟀습니다. 24,000프레임 예산, 연구실 게임, 같은 감지기. ### 처음 낸 숫자는 믿을 게 못 됐습니다 처음에는 정책마다 한 번씩만 돌려 표를 만들었습니다. 그런데 다시 돌리니 평활 난수가 55% 에서 78% 로 튀었습니다. **한 번 측정한 난수 정책의 숫자는 아무 의미가 없습니다.** 심사자가 다시 돌리면 다른 값이 나오는 표를 싣고 있었던 겁니다. 그래서 난수를 `Math.random` 에서 **시드 고정 PRNG** 로 바꾸고, 회차를 나눠 평균과 범위를 냅니다. `node policy_bench.mjs` 로 그대로 재현됩니다. 여기에 함정이 하나 더 있었습니다. **초파리 정책도 결정적이 아닙니다** — 감각 뉴런 강제 발화가 포아송 과정이라 난수를 씁니다. 난수 정책만 시드를 고정해 놓고 "이 표는 재현된다"고 쓰고 있었습니다. `Brain.rng` 를 갈아끼울 수 있게 해서 뇌의 난수까지 고정했고, 같은 시드로 두 번 돌려 조향값·최종 위치·누적 스파이크(5,576,128)까지 완전히 일치하는 것을 확인했습니다. | 정책 | 회차 | 탐색률(평균) | 범위 | **증상 종류(평균)** | 관측된 종류 | |---|---|---|---|---|---| | 초파리 커넥톰 | 3 | **42%** | 35–50% | **2.7종** | STUCK, OOB, NOPROG, DOORLOCK | | 평활 난수 | 5 | 61% | 57–66% | 3.2종 | 다섯 종 전부 | | 균등 난수 | 5 | 50% | 46–52% | 3.6종 | STUCK, OOB, DOORLOCK, FREEZE | | 직진만 | 3 | 5% | 5–5% | 1.0종 | FREEZE | **초파리가 집니다.** 탐색률 42% 대 61% 로 범위가 전혀 겹치지 않고, 한 판에 닿는 증상 종류도 두 난수 정책보다 적습니다. 이기는 항목이 하나도 없습니다. 회차가 3~5회라 유의성을 주장하지는 않습니다. 범위가 겹치지 않는다는 것만 적습니다. 그리고 여기서 QA 지표에 대한 교훈이 하나 나옵니다. **"버그 수"는 가짜 지표입니다.** 「직진만」이 95건으로 최다인데 전부 같은 정지 버그 하나입니다. 의미 있는 것은 **탐색률과 증상의 종류**입니다. ### 그러면 커넥톰이 난수 위에 뭘 보태기는 하는가 초파리가 단독으로 지는 것은 쟀습니다. 남는 질문은 하나였습니다 — 커넥톰이 난수 위에 무언가를 보태는가? 이건 의견이 아니라 측정으로 답할 수 있는 질문입니다. 프레임워크가 정책을 갈아끼울 수 있으니까요. 그래서 **짝지은 대조군**을 만들었습니다. 초파리 항만 빼고 난수 항은 그대로 두고, **같은 시드를 줘서 완전히 같은 난수열**을 보게 합니다. > **이 절은 세 번 고쳐 썼습니다 (2026-09-18).** 처음 답은 "보태는 게 없다(−3%p)"였습니다. > 조향 편향 버그를 고치자 **+11.4%p 로 부호가 뒤집혔습니다.** 거기서 멈췄으면 > "커넥톰이 난수를 이긴다"를 발표할 뻔했습니다. 더 엄격한 대조군을 만들어 다시 재니 > **다시 0 으로 돌아왔습니다.** 세 단계를 전부 남깁니다 — 중간에 멈췄을 때 무엇을 > 발표하게 되는지가 이 절의 요점이기 때문입니다. #### 1차 — 보태는 게 없다 (그러나 우리 버그가 섞여 있었다) | | 탐색률 | 증상 종류 | |---|---|---| | 섞은 정책 (초파리 + 난수) | 53% | 3.7종 | | 대조군 (난수만, 같은 열) | **56%** | 3.3종 | | 차이 | **−3%p** | 범위 겹침 | 이때 초파리의 조향에는 **+0.044 의 잔류 편향**이 남아 있었습니다(1.8절). 제자리에서 도는 신호로 재고 있었던 겁니다. 결론은 맞았지만 근거는 오염돼 있었습니다. #### 2차 — 편향을 고치니 부호가 뒤집혔다 적응 상수 하나를 고친 뒤 12회차로 짝지어 다시 쟀습니다. | | 탐색률 | |---|---| | 섞은 정책 (초파리 + 난수) | **62.8%** | | 대조군 (난수만, 같은 열) | 51.5% | | 차이 | **+11.4%p** · 표준오차 2.5 · **t = 4.63** (자유도 11, 임계값 2.201) | 12회차 중 10회차가 양수고, 최대 이상치를 빼도 +9.7%p 입니다. 통계적으로 확실히 섭니다. **여기서 멈추고 싶은 유혹이 있었습니다.** 이건 이 프로젝트가 몇 주간 실어온 음성 결론을 뒤집는 결과고, 대회에 낼 문장이 됩니다. #### 3차 — 그런데 눈을 가려도 똑같이 합니다 섞은 정책은 `초파리*0.6` 을 더합니다. 그래서 대조군보다 **조향 진폭 자체가 큽니다.** +11.4%p 가 커넥톰의 '정보' 때문인지 그냥 '더 크게 꺾어서'인지 위 실험은 구분하지 못합니다. 그래서 `mismatch` 대조군을 만들었습니다. **뇌에는 다른 시드의 판을 보여주고, 그 출력으로 지금 판을 몹니다.** 조향 신호의 진폭·시간 구조·통계는 섞은 정책과 같고, **화면과의 관련성만 없습니다.** 눈을 가린 초파리입니다. | | 탐색률 | |---|---| | 섞은 정책 (제대로 된 판을 본다) | 63.5% | | `mismatch` (엉뚱한 판을 본다) | **65.4%** | | 차이 | **−3.2%p** · 표준오차 3.2 · **t = 0.98** (임계값 2.201) → **구분되지 않음** | 세 정책을 나란히 놓으면 이렇게 분해됩니다. | 정책 | 탐색률 | 잡음 | 초파리 모양 신호 | 화면과의 관련성 | |---|---|---|---|---| | 대조군 (`weak`) | 51.5% | O | ✗ | — | | `mismatch` | 65.4% | O | O | **✗** | | 섞은 정책 (`hybrid`) | 63.5% | O | O | O | **+11.4%p 는 전부 진폭과 시간 구조의 몫이고, 화면과의 관련성이 보태는 것은 0 입니다.** 눈을 가린 초파리가 눈 뜬 초파리만큼 합니다. 그러니 결론은 1차와 같습니다 — 커넥톰은 보태지 않습니다. 다만 1차는 **옳은 결론을 잘못된 근거로** 내고 있었습니다. 진폭이 다른 대조군과 비교하고 있었으니까요. ### 우리도 부풀리고 있었습니다 "버그 개수는 가짜 지표"라고 써놓고, 정작 맵 이탈을 픽셀 버킷으로 나눠 세고 있었습니다. (321,20)과 (328,20)은 **같은 탈출**인데 두 건으로 올라갔습니다. 넘은 변 기준으로 묶도록 고쳤고, 그 결과 위 표의 건수가 전부 줄었습니다. 결론은 바뀌지 않았습니다. 이 결과를 숨기지 않고 화면 전면에 싣습니다. 제품은 정책이 아니라 프레임워크이고, 프레임워크는 꽂히는 어떤 정책보다 오래 살기 때문입니다. ## 2. 그 대신 확인한 것 | 주장 | 근거 | |---|---| | 진짜 커넥톰 138,639뉴런이 브라우저에서 돈다 | 초당 1,321스텝, 283만 스파이크/초 | | 시각 자극이 하행뉴런까지 전파된다 | 눈 150Hz 자극 시 하행뉴런 247개가 1Hz 이상 발화 | | 정적 조건에서 좌우를 구분한다 | 분리도 0.95 (왼쪽 −0.41 / 오른쪽 +0.75) | | **조향은 오직 뇌 출력에서만 나온다** | 조향 채널 40개(전체의 0.03%)를 자르면 분리도 0.95 → 0.00, 복구하면 1.12 | | 버그를 찾는다 | 24,000프레임에 평균 55건, 심어둔 4종 중 3종 도달 | | 100% 재현된다 | 앱 안 12/12 완전 일치. 내보낸 리포트에서 꺼낸 재현 파일도 Node 에서 `verify_repro.mjs` 로 완전 일치 | **손상 실험이 이 프로젝트의 핵심 증거입니다.** 조향을 스크립트로 보정한 구현은 뉴런을 아무리 잘라도 똑같이 움직입니다. 우리 초파리는 멈춥니다. ## 2.5 남의 게임에 붙습니다 — 되는 것과 안 되는 것 "제품은 정책이 아니라 프레임워크"라는 주장은 우리가 한 번도 본 적 없는 게임에 붙을 수 있어야만 참입니다. 그래서 「Yours」 근무지를 만들었습니다. HTML 게임을 떨어뜨리면 그대로 붙습니다. **원리.** `blob:` URL 로 만든 문서는 만든 쪽의 출처를 물려받습니다. 그래서 iframe 안인데도 `contentDocument` 를 읽을 수 있고 캔버스 픽셀도 읽을 수 있습니다. 게임 스크립트보다 먼저 `getContext` 를 가로채 `preserveDrawingBuffer` 를 켜야 하므로, HTML 맨 앞에 심(shim)을 끼워 넣은 뒤 blob 으로 다시 만듭니다. 입력은 iframe 의 window/document/캔버스 세 곳 모두에 합성 `KeyboardEvent` 를 쏩니다. 어디서 듣는지 모르기 때문입니다. **되는 것** | | | |---|---| | 자체 완결형 HTML 파일 (드래그&드롭) | 됩니다 | | CORS 를 열어둔 URL | 됩니다 (받아서 다시 띄웁니다) | | 캔버스에 그리는 게임 | 됩니다 — 2D·WebGL 모두 | | 정지·반복 감지 | 됩니다 (연구실과 같은 감지기) | | 표적이 죽으면 새 판 | 됩니다 (연구실의 끼임 재시작과 같은 이유) | **안 되는 것 — 숨기지 않습니다** - **CORS 를 막은 URL.** 브라우저가 내용을 주지 않습니다. 우회할 방법이 없고, 우회하는 척하면 거짓말입니다. 그래서 "파일을 저장해서 떨어뜨려 달라"고 정확히 말합니다. - **DOM 게임.** 캔버스가 없으면 20초 뒤에 포기하고 그렇게 말합니다. - **오염된 캔버스.** 게임이 CORS 없이 외부 이미지를 그렸으면 `getImageData` 가 막힙니다. 이때 초파리는 앞이 안 보입니다. 화면에 `Canvas unreadable` 로 씁니다. - **정확한 재현.** 외부 게임은 시드를 모르고 내부 상태도 못 봅니다. 입력열은 남기지만 **12/12 완전 일치는 연구실에서만 보장됩니다.** 외부 표적의 재현은 게임이 결정적일 때만 됩니다. - **심어둔 정답이 없습니다.** 외부 게임에서는 "몇 종 중 몇 종을 찾았나"를 잴 수 없습니다. 증상만 셉니다. 샘플 표적(`docs/samples/rover.html`)에는 프리즈와 화면 반복을 하나씩 심어 놨습니다. 우리 코드를 전혀 모르는 자체 완결형 HTML 이고, 붙이면 프리즈를 250프레임 안에 찾습니다. ## 3. 모델은 균일 LIF입니다 - 누출 적분 발화 뉴런. 호지킨-헉슬리 채널도, 수상돌기 구획도, 가소성도 없습니다. - 신경전달물질은 흥분/억제 부호로만 반영합니다. 수용체별 동역학은 없습니다. - 모든 뉴런이 같은 파라미터를 씁니다. 실제로는 세포 유형마다 다릅니다. ## 4. 커넥톰을 쳐냈습니다 - 시냅스 5개 미만 연결을 버려 1,509만 쌍 중 270만 쌍만 남겼습니다(시냅스의 62.7%). - 남은 시냅스 가중치를 `0.275 / 0.627 ≈ 0.44` 로 보정했습니다. 가지치기 보정이지 성능을 맞추기 위한 튜닝이 아닙니다. - 뉴런의 96.8%는 여전히 연결이 남아 있습니다. ## 5. 시각 입력은 광수용체가 아닙니다 FlyWire FAFB는 망막과 라미나가 온전하지 않습니다. 광수용체 R1-6의 출력 연결이 평균 **1.7개**뿐입니다(전체 평균 19.5). 31%는 연결이 아예 없습니다. 그래서 한 단계 안쪽인 **LA>ME(라미나→수질, 8,029뉴런)** 에 넣습니다. 생물학적으로는 L1~L5가 광수용체 신호를 받는 첫 중계소이므로 타당하지만, 광수용체 자체의 처리 단계는 건너뜁니다. ## 6. 시야는 전처리를 거칩니다 날것의 화면을 그대로 넣으면 뇌가 반응하지 않습니다. 실측한 민감도는 이렇습니다. | 평균 밝기 | 좌우 신호 변화폭 | 잡음 | 판정 | |---|---|---|---| | 0.25 | 0.073 | 0.080 | 신호 < 잡음 | | 0.50 | 0.439 | 0.080 | 신호 5배 | 그래서 중심 밝기를 0.5로 올리고, 좌우 평균 차이를 3.2배 증폭하고, 시그모이드로 부드럽게 포화시킵니다. 라미나의 측면억제와 같은 종류의 처리이지만, **이득 값은 우리가 정한 것이지 측정에서 나온 것이 아닙니다.** ## 7. 속도 비교는 조건이 다릅니다 `webgpu-fly`가 같은 기계(M2 Pro)에서 잰 값이 NEST 0.67kHz, Rust 0.45kHz, WebGPU 0.25kHz입니다. 우리는 1.32kHz이지만 **연결 수가 다릅니다** — 저들은 2,030만, 우리는 270만입니다. 연결당 처리량으로는 비슷하거나 저들이 높습니다. 더 빠르다고 주장하지 않습니다. ## 7.5 저작권 - 커넥톰과 주석은 **CC-BY 4.0** 이라 재배포할 수 있습니다. - 둠은 id Software 가 셰어웨어 판의 자유 재배포를 허용했고, 그 WAD 가 wasm 모듈에 들어 있습니다. - **GTA1 자산은 이 저장소에 없습니다.** Carnage3D 엔진은 MIT 지만 게임 자산은 Rockstar/Take-Two 저작물이며, 2004년 무료 배포는 재배포 허가가 아닙니다. 그래서 원저자의 GitHub Pages 에서 직접 불러옵니다. 사용자가 원저자 데모 페이지를 여는 것과 동일한 요청입니다. 다만 원저자 호스팅이 사라지면 GTA1 근무지는 동작하지 않습니다. ## 8. 검증하지 않은 것 - 브라우저·GPU별 동작 (개발은 Safari/Chrome + Apple Silicon에서만 했습니다) - 여러 직원을 동시에 고용했을 때의 실제 병렬 성능 - 둠에서의 버그 검출 능력 (둠에는 심어둔 버그가 없어 정지·반복만 봅니다) - 우리 테스트 게임 외의 실제 상용 게임에서의 유용성