New
Apps 1,895Makers 1,890This week 1,690
Promote your app−30%
0views in
the last 30 days

Fly Worker

QA fuzzer for browser games with deterministic bug detection and replayable reports

Built with Claude Code

by hwkim3330 · Unclaimed · from KRSouth Korea · added Oct 29, 2026
no ratings0 comments

Fly Worker

A QA fuzzer for browser games. One of its policies is a real fruit-fly connectome — and that one loses.

🔗 Live: https://hwkim3330.github.io/flyworker/ · https://huggingface.co/spaces/kimhyunwoo/flyworker

Fly Worker drives a browser game with synthetic key input, watches only the framebuffer, flags bugs with five deterministic rules, and writes an HTML report with evidence frames and a replay file that reproduces each finding. One of the swappable steering policies is a spiking simulation of the FlyWire v783 fruit-fly connectome (138,639 neurons); it is benchmarked against random baselines and published even though it loses.

Fly Worker running GTA1 with the connectome policy


브라우저 게임을 스스로 플레이해 버그를 찾고, 찾은 것을 그대로 재현하는 QA 퍼저. 정책 하나가 초파리 전뇌 커넥톰 138,639뉴런입니다. 그리고 그 정책은 난수에게 집니다. LLM 추론 0회.


제품은 프레임워크입니다

 ┌── 붙인다 ────────────────────────────────────────────┐
 │ 합성 키 입력 + 프레임버퍼 가로채기.                   │
 │ 게임 소스도, 게임 쪽 인터페이스도 필요 없습니다.       │
 └──────────────────────────────────────────────────────┘
 ┌── 조종한다 ──────────────────────────────────────────┐
 │ 정책 = 화면을 받아 [조향, 전진]을 내놓는 함수 하나.    │
 │ 초파리 커넥톰 · 평활 난수 · 균등 난수 · 직진          │
 └──────────────────────────────────────────────────────┘
 ┌── 판정한다 ──────────────────────────────────────────┐
 │ 결정적 규칙 5개. 어떤 모델도 "이건 버그다"라고         │
 │ 말하지 않습니다.                                      │
 └──────────────────────────────────────────────────────┘
 ┌── 내놓는다 ──────────────────────────────────────────┐
 │ 증거 사진과 재현 파일이 들어 있는 HTML 리포트 한 장.   │
 └──────────────────────────────────────────────────────┘

정책은 부품이고 프레임워크가 제품입니다. 그래서 우리 정책이 져도 이 제품은 섭니다.

우리 초파리는 집니다 — 재서 싣습니다

같은 프레임워크, 같은 계산량(24,000프레임), 같은 감지기. 정책만 교체했습니다. 난수는 시드를 고정했고 회차를 나눠 평균과 범위를 냅니다.

정책 맵 탐색률 (평균) 범위 증상 종류 (평균) 관측된 종류
초파리 커넥톰 50% 40–55% 2.0 OOB, STUCK
섞은 정책 (초파리+난수) 62% 51–74% 3.3 다섯 종 전부
눈 가린 초파리 (엉뚱한 판을 봄) 66% 55–74% 3.3 다섯 종 전부
초파리 + 온라인 학습 33% 24–48% 3.3 STUCK, OOB, DOORLOCK, NOPROG
평활 난수 63% 59–66% 3.3 다섯 종 전부
균등 난수 49% 46–52% 3.7 STUCK, OOB, DOORLOCK, FREEZE
직진만 (대조군) 5% 5–5% 1.0 FREEZE

커넥톰은 화면에 대해 아무것도 보태지 않습니다. 섞은 정책은 같은 난수열만 쓰는 짝 대조군을 +11.4%p 로 이깁니다(12회차 짝지음, t = 4.63). 그런데 뇌에 완전히 다른 판을 보여주고 그 출력으로 모는 정책이 66% 로, 제대로 보는 62%와 구분되지 않습니다 (−3.2%p, t = 0.98). 커넥톰이 주는 것은 조향 진폭과 시간 구조지 화면의 정보가 아닙니다.

이 결론에 이르기까지 세 번 고쳐 썼습니다. 처음엔 −3%p("보태는 게 없다"), 우리 디코더의 조향 편향을 고치자 +11.4%p 로 부호가 뒤집혔고, 진폭까지 맞춘 대조군을 만들자 다시 0 으로 돌아왔습니다. 2차에서 멈췄으면 "커넥톰이 난수를 이긴다"를 통계까지 붙여 발표했을 겁니다. 세 단계 전부 LIMITATIONS 1.9 에 있습니다.

빙빙 도는 것은 우리 버그였습니다. 조향에 남은 +0.044 편향 때문에 초파리가 320×240 판에서 선회반경 40px 원을 그리고 있었습니다. 적응 상수 하나(1/40 → 1/10)로 편향이 0.008 로, 탐색률이 42 → 50% 가 됐습니다. 위 표의 초파리 계열 숫자는 전부 수정 후 값입니다.

온라인 학습은 우리 버그를 보상하고 있었습니다. 편향이 남아 있을 때는 판독 위의 학습이 +12.0%p(t = 3.12) 도움이 됐는데, 편향을 직접 걷어내자 같은 학습이 −25.6%p(t = 4.01) 해가 됐습니다. 대조군 없이 "학습을 붙였더니 좋아졌다"를 쓰면 자기 결함을 덮고 있는 것일 수 있습니다. 판독을 통째로 대체하는 형태(learn)는 steering() 을 쓰지 않아 영향이 없고, 짝 대조군 대비 +13.1%p(t = 3.15) 로 그대로 섭니다.

버그 개수는 가짜 지표입니다. 「직진만」이 95건으로 최다인데 전부 같은 정지 하나입니다. 그래서 탐색률과 증상 다양성으로 잽니다. 그 기준으로 초파리는 평활 난수에 집니다.

이 결과를 첫 화면에 싣습니다. 숨기면 제품이 정책에 인질로 잡히기 때문입니다.

그래도 커넥톰이 정말 조종하는가 — 잘라보면 됩니다

말로 주장하는 대신 손상 실험을 넣었습니다. 화면에서 직접 눌러볼 수 있습니다.

조작 빛 방향 분리도
정상 0.947
조향 채널 40개 절단 (전체의 0.03%) 0.000
복구 1.121
왼쪽 눈 2,000개 절단 0.059 (좌우를 못 가림)

138,639개 중 40개만 끊으면 조종이 완전히 죽습니다. 조향을 스크립트로 보정한 구현은 이 시험을 그대로 통과합니다. 우리 것은 멈춥니다.

프로젝트 조향을 누가 하는가
abgunaydin/webgpu-fly "the connectome does not walk the fly" — 손으로 짠 sin(t×10Hz) 보행을 스케일
Xenova/fruit-fly-simulation "Engineered rate decoder" · "movements are illustrative, not validated"
snedea/flybrain 뇌 기여를 clamp(-0.05, 0.05) 로 누르고 방향은 atan2 로 직접 계산
nftechie/doomfly 도파민 가소성까지 붙였지만 자기 검증을 통과하지 못했다고 적어놨습니다
이 프로젝트 하행뉴런 출력만. 보정 없음

근무지 네 곳

GTA1 codenamecpp/carnage3d (MIT). 얇은 인터페이스가 없어서 합성 KeyboardEvent 로 몰고 getContext 를 가로채 WebGL 프레임버퍼를 읽습니다. 자산은 원저자 페이지에서 직접 받아옵니다(재배포 안 함)
DOOM jacobenget/doom.wasm. import 10 / export 4. 선형 메모리에서 프레임버퍼를 직접 읽습니다
연구실 버그 4종을 일부러 심어둔 테스트 게임. 감지기는 어디 심겼는지 모릅니다. 재현을 증명하는 곳
Yours 당신의 HTML 게임. 떨어뜨리면 붙습니다 — 아래 참조

처음 보는 게임에도 붙습니다

"프레임워크가 제품"이라는 주장은 우리가 본 적 없는 게임에 붙어야만 참입니다. 「Yours」에 HTML 게임을 떨어뜨리면 그 자리에서 붙습니다.

blob: 문서는 만든 쪽 출처를 물려받으므로 iframe 안이어도 캔버스를 읽을 수 있습니다. 게임 스크립트보다 먼저 getContext 를 가로채야 해서 HTML 맨 앞에 심을 끼워 넣고 blob 으로 다시 만듭니다. 입력은 어디서 듣는지 모르므로 window·document·캔버스 세 곳에 쏩니다.

안 되는 것도 화면에 그대로 씁니다 — CORS 를 막은 URL(우회 불가), DOM 게임, 오염된 캔버스, 그리고 외부 표적의 정확 재현(시드도 내부 상태도 모릅니다). 자세한 것은 LIMITATIONS.md §2.5.

버그를 찾고, 재현하고, 리포트를 낸다

감지기는 버그가 어디 심겼는지 모릅니다. 증상만 봅니다. 전부 결정적 규칙입니다.

증상 판정
화면 정지 상태가 90프레임 동안 불변
끼임 300프레임 동안 6×6픽셀을 못 벗어남
맵 이탈 좌표가 맵 밖 (넘은 변 기준으로 한 사건 = 한 건)
문 잠김 열쇠 획득 후 240프레임 경과했는데 문이 닫혀 있음
진행 불가 1800프레임 동안 목표에 더 가까워지지 못함

게임은 (시드 + 입력열)에 대해 결정적이므로 입력열만 저장하면 그대로 되살아납니다. 실제 QA에서 가장 괴로운 "재현이 안 되는 버그"가 구조적으로 사라집니다.

재현 검증 12/12 완전 일치 (입력을 반올림하면 1500프레임쯤에서 1픽셀씩 어긋나므로 원값을 저장합니다)

「Download report」를 누르면 자체 완결형 HTML 한 장이 나옵니다. 발견 순간의 화면(PNG)과 재현 파일(JSON)이 안에 들어 있어 인터넷 없이 열립니다.

그 파일이 앱 밖에서도 되살아나는지 따로 확인할 수 있습니다. 브라우저 안에서 Replay 버튼이 도는 것과, 개발자가 받아간 파일이 다른 곳에서 도는 것은 다른 주장이고, QA 도구의 값어치는 후자에 있습니다.

node verify_repro.mjs ~/Downloads/flyworker_report_2026-09-16.html
#   STUCK  630프레임 → (144,99) vs 기록 (144,99)  일치
#   STUCK  751프레임 → (179,20) vs 기록 (179,20)  일치
#   2/2 일치

어떻게 게임을 하는가

게임 1인칭 시야 (레이캐스팅, 겹눈 화각 270°)
   ↓  좌/우 눈에 각각 주입 (시각엽 망막위상 좌표로 대응)
LA>ME 라미나→수질 8,029뉴런
   ↓  실제 커넥톰 회로
하행뉴런 1,299개 중 좌우 차등 반응 채널
   ↓
조향 ← →     전진

수습 교육. 어떤 하행뉴런이 "왼쪽 시야"에 반응하는지는 개체마다 다릅니다. 이름이 알려진 조향 뉴런(DNa01·DNa02)은 좌우 각 1개뿐이라 표본이 너무 작습니다. 그래서 고용 직후 좌/우 눈에 번갈아 빛을 비춰 차등 반응하는 하행뉴런을 골라냅니다(약 1.5초). 집단 부호화입니다. 각 채널을 자기 자신의 균형 조명 반응으로 나눠 쓰므로 개체마다 다른 이득 차이가 자동으로 상쇄됩니다.

측정값

항목 값
커넥톰 138,639 뉴런 / 2,700,513 연결 / gzip 7.45MB (연결당 2.01B)
시뮬레이션 초당 1,321스텝, 283만 스파이크/초
게임 165fps
수습 교육 1.4초 → 좌채널 20 / 우채널 20 / 활성 하행뉴런 286
빛 방향 분리도 1.158 (왼쪽 −0.409 / 오른쪽 +0.749)

직원 한 명 = 워커 하나. 커넥톰은 읽기 전용이고 개체별 상태는 0.5MB뿐이라 코어 수만큼 고용해 진짜 병렬로 일을 시킬 수 있습니다.

정직하게 — 한계

  • 닫힌 고리 시각 주행은 되지 않습니다. 조향과 빛 방향의 상관계수는 8시드에서 0입니다. 디코더를 네 가지로 바꿔도 전부 0이었습니다.
  • 이유도 측정했습니다. T4 는 L1·L2 에서 연결을 하나도 받지 않고(직접 세어 확인), 수질을 거쳐 들어오는 신호가 단계마다 10배씩 줄어 31 → 21 → 2.6 → 0.56 → 0.00Hz 로 꺼집니다. 타이밍이 아니라 감쇠입니다. 시냅스 이득을 올리면 T4 는 울지만 망 전체가 같이 과흥분합니다. (예전에 적어둔 "ON 경로 부호 반전" 설명은 틀렸습니다 — 철회 기록)
  • 균일 LIF입니다. 수용체별 동역학도, 가소성도, 뉴런별 파라미터도 없습니다.
  • 시냅스 5개 미만 연결을 쳐내 62.7%만 남기고 가중치를 0.275 / 0.627 ≈ 0.44 로 보정했습니다.
  • FlyWire는 망막·라미나가 불완전해(광수용체 출력 연결 평균 1.7개) 시각 입력을 LA>ME에 넣습니다.

전부 LIMITATIONS.md 에 있습니다. 실패한 가설과 버린 시도까지 적어뒀습니다.

개발

python3 -m venv .venv && .venv/bin/pip install pandas pyarrow numpy
bash tools/00_fetch.sh          # 원본 데이터 (~135MB)
.venv/bin/python tools/pack.py  # 커넥톰 → docs/data/brain.bin
.venv/bin/python tools/meta.py  # 뉴런 역할 → docs/data/meta.json
python3 -m http.server 8877 --directory docs

node policy_bench.mjs           # 위 정책 비교표를 다시 재본다
PAIRED=6 node policy_bench.mjs  # 온라인 학습 vs 같은 섭동열 대조군 (회차별로 짝지어 비교)
WSYN=1.5 ONLY=fly node policy_bench.mjs   # 시냅스 이득을 올리면 성능이 어떻게 되는가
node verify_repro.mjs <리포트>  # 내보낸 재현 파일이 앱 밖에서 되살아나는지 확인

시각 운동 경로(T4)가 왜 울지 않는지 파본 진단 스크립트도 그대로 뒀습니다. LIMITATIONS.md 의 표는 전부 이것들이 낸 숫자입니다.

node wiring.mjs      # T4 가 무엇에서 입력을 받는가 (배선을 직접 센다)
node chain.mjs       # 시각신경 → L1/L2 → 수질 → T4 단계별 발화율
node tau_test.mjs    # 뉴런별 시상수가 T4 를 깨우는가 (아니었다)
node gain.mjs        # 시냅스 이득을 올리면 어디까지 깨어나는가

출처와 라이선스

  • 커넥톰: FlyWire v783 (CC-BY 4.0) — Dorkenwald et al., Schlegel et al.
  • 뉴런 주석: flywire_annotations (CC-BY 4.0)
  • LIF 파라미터: Shiu et al. 커넥톰 기반 모델
  • DOOM: jacobenget/doom.wasm · GTA1: codenamecpp/carnage3d (MIT)
  • 코드: MIT

Description taken from the repository README.

Ratings

–
0 ratings
5 0
4 0
3 0
2 0
1 0

Rate this app

Sign in to rate

Comments 0

Sign in to comment

No comments yet.

Related apps

#qa · #fuzzing · #games