나루 · Naru

루프 대시보드 — 굽힌 시각 2026-09-14 16:13:48

마지막 바퀴바퀴 15초록 · 2026-09-14
계약ALL GREEN2026-09-14 16:13
진행20 / 50백로그 항목
누적 비용$55.5908

진행 — 한 줄 = 한 바퀴

P0 하네스: 게임보다 검사가 먼저 선다 7/7
  • Godot 4.7 프로젝트 뼈대 + 헤드리스 테스트 러너 + 프로젝트 로컬 격리
  • check.sh 3단계 — import → parse → tests 순서 (class_name 캐시를 임포트가 만든다)
  • 계약 배선 — criteria.tsv + run-contract.sh, 증거가 results.json 에 기계로 쓰인다
  • 세션 지시서 PROMPT.md — 읽기 → 만들기 → 자체 QA → 커밋, 정지 규칙과 red lines
  • 대조군 절차를 스크립트로 만든다 — 검사를 하나 일부러 깨뜨렸을 때 빨개지는지 확인
  • 화면 QA — tools/loop/shot.sh — Godot 자신의 프레임버퍼를 PNG 로 굽는다.
  • 드라이버 loop.sh · ctl.sh — 백로그 한 줄 뽑기 → 세션 → 계약 → 초록이면 다음.
P1 걸어다니는 세계 12/12
  • 색 네모 플레이어가 48px 타일 위를 속도 240 으로 8방향 이동 (대각선 정규화)
  • 논리 960×540 · 정수 2배 · 보이는 칸 20 × 11.25
  • 마우스가 방향을 정한다 — 이동 방향과 별개. 4방향 스냅 + 히스테리시스
  • 시드 기반 월드 생성 256×256, 땅/바다 2종, 같은 시드 = 같은 월드
  • 이동 충돌 — 바다에 못 들어가고, 해안에 비스듬히 붙으면 미끄러진다
  • 카메라가 플레이어를 따라간다. 줌 없음 (해상도가 시야 이득이 되면 안 된다)
  • 월드를 화면에 그린다 — 보이는 칸만. main.gd 의 임시 격자를 걷어낸다
  • measure_draw.gd걷는 구간에 대조군을 붙이거나 그 구간을 뺀다
  • measure_facing.gd 가 사람의 마우스에 안 흔들리게 한다 — 진짜 커서를 뺏어 재는
  • 계약이 check.sh tests 를 두 번 돈다 — 기준 3(mintests.sh)과 기준 4 가
  • 창을 띄우는 게이트가 포커스를 안 뺏는다못 막는다. 항목이 시킨 대로 닫는다
  • 창을 띄우는 게이트를 한 프로세스로 합친다tools/tests/measure_window.gd
P2 손: 인벤토리와 도구 1/9
  • 지형 타일 48 → 32px — 고정값이 바뀌었다(위 표). 캐릭터 48px 과 속도 240 px/s 는
  • 캐릭터 네모를 1칸 폭으로 · 충돌 상자를 발밑에 — 지금 그리는 네모는 48×48(1.5칸 ×
  • 인벤토리 코어 (순수 클래스) — 일반 18칸, 꽉 찼을 때 넘치는 몫이 안 사라진다
  • 화면 아래 상시 핫바 9칸 + 숫자키로 손에 들기
  • 좌클릭 = 손에 든 것의 동작. 대상이 없어도 사용 모션이 나온다
  • 월드 오브젝트 배치 — 나무 · 돌 · 광물 1종을 시드로 놓는다
  • 벌목 — 도끼로 나무를 베면 목재가 바닥에 떨어진다
  • 채광 — 곡괭이로 돌/광물을 캔다
  • 바닥 드롭 + 걸어가서 줍기. 인벤토리가 꽉 차면 바닥에 남는다
P3 만드는 것 0/5
  • 제작대 1개 + 레시피를 데이터로 ({inputs, output, amount})
  • 수량 지정 → 재료 선소모 → 타이머 → 출력 버퍼 → 수동 수령
  • 수령 시 인벤토리가 모자라면 들어가는 만큼만 들어가고 나머지는 버퍼에 남는다
  • 도구 3종을 실제로 제작한다 — 도끼 · 곡괭이 · 총
  • ?[ASK] 제작 타이머 기본값 — 임시로 1개당 5초. 실제로 돌려보고 사람이 정한다
P4 기르는 것 0/5
  • 게임 내 시계 — 하루 20분(낮 10 + 밤 10), 화면이 밤에 어두워진다
  • 밭 일구기 / 심기 / 물주기 / 수확. 간 밭을 다시 치면 흙으로 돌아간다
  • 물 준 날에만 한 단계 자란다. 안 주면 안 자랄 뿐 죽지 않는다
  • 조리 → 버프 음식 1종. 먹으면 실제로 스탯이 오르고 시간이 지나면 풀린다
  • ?[ASK] 작물 성장 단계 수와 하루당 진행량 — 임시값으로 만들고 사람이 플레이해서 정한다
P5 나가는 것: 침몰한 마을 (축소판) 0/8
  • 선착장 — 올라타면 원정 맵으로 넘어가고, 끝나면 내 섬으로 돌아온다
  • 원정 맵 생성 — 좁은 실내, 잔해로 막힌 통로
  • 잔해를 곡괭이·도끼로 뚫어 길과 보물을 찾는다
  • 몰려오는 적 — 웨이브로 밀려오고 좁은 통로에서 막힌다
  • 총으로 적을 처치한다. 체력 · 죽음
  • 끝나는 조건 — 버티기를 완료하면 탈출로가 열린다. 한 판에 걸린 시간을 로그로 남긴다
  • 원정 산출물 1종(상위 금속)이 드롭된다
  • ?[ASK] 웨이브 수와 잔해 채굴 시간의 비율 — 「막으면서 캐야 한다」가 성립하는 값. 눈으로만 정할 수 있다
P6 한 바퀴가 닫힌다 0/4
  • 원정 산출물로 더 좋은 도구 1종을 만든다
  • 그 도구로 원정이 실제로 쉬워지는지 잰다 (잔해 채굴 시간 · 클리어 시간)
  • 버프 음식을 먹고 간 판과 안 먹고 간 판의 차이를 잰다 — 「보급」이 실제로 듣는가
  • ?[ASK] 사람이 직접 끝까지 플레이한다. 한 바퀴가 재미있는가? — 이 판정만이 프로토타입의 결론이다

계약 — 기계가 채점한다 무장 cff70e4b73ac

세션에게 「검사를 약하게 하지 마세요」라고 부탁하지 않는다. criteria.tsv 는 해시로 잠겨 있고, 고치면 채점자가 exit 77 로 죽는다.

1 임포트 게이트가 깨끗하다 (전역 클래스 캐시를 만든다) 초록
tools/loop/check.sh import
  • IMPORT ok
2 모든 GDScript 가 파스된다 초록
tools/loop/check.sh parse
  • PARSE 26개 스크립트, 실패 0
3 단위 테스트가 통과하고 개수가 줄지 않는다 초록
bash tools/loop/mintests.sh 66
  • TESTS 66 passed, 0 failed
4 색 네모 플레이어가 48px 타일 위를 속도 240 으로 4방향 이동 초록
tools/loop/check.sh tests
  • TESTS 66 passed, 0 failed
5 매 바퀴 읽는 문서가 부풀지 않았다 초록
bash tools/loop/doclen.sh CLAUDE.md:45 docs/PROMPT.md:70 .loop/state.md:90
  • DOCLEN CLAUDE.md 41/45줄
  • DOCLEN docs/PROMPT.md 60/70줄
  • DOCLEN .loop/state.md 63/90줄
6 월드를 화면에 그린다 — 보이는 칸만. main.gd 의 임시 격자를 걷어낸다 초록
tools/loop/shot.sh /tmp/w.png
  • SHOT 960x540 색 370개 가장 넓은 한 색 0.9% → /tmp/w.png

바퀴 일지 — 무엇이 막았고, 왜, 그래서 무엇을 바꿨나

전체는 JOURNAL.md. 아래는 그 파일을 그대로 읽어 온 것이다.

바퀴 15

지형 타일 48 → 32px — 고정값이 바뀌었다(위 표). 캐릭터 48px 과 속도 240 px/s 는

2026-09-14 초록
문제코드에 48 이 12군데 박혀 있었다. 그중 하나(WorldCollide.HALF = 22)는 그냥 숫자를 바꾸는 것으로 끝나지 않았다 — 22 를 두면 몸 44px 이 통로 32px 보다 넓어져 1칸 통로에 끼는데, 단위 검사 스무 개 남짓이 전부 손 계산한 48 기준 기대값이라 어느 것도 안 빨개진다. 그리고 실측 게이트 measure_collide 의 미끄럼 구간(1.2초)은 세로로 203.7px 를 가는데 곧은 해안 7줄이 336px 에서 224px 로 줄어 판정이 무너졌다.
원인고정값 하나가 자(尺)이자 게이트의 기대값이자 검사의 손 계산이라는 세 역할을 동시에 하고 있었다. 자를 바꾸면 나머지 둘이 따라와야 하는데, 박힌 숫자는 안 따라온다. 게이트의 시간 상수(1.2초)는 한술 더 뜬다 — 픽셀이 아니라 로 적혀 있어서 타일이 작아진 것과 상관없어 보이지만, 실제로 재는 것은 「몸이 해안 몇 줄을 지나가나」다.
고친 것48 을 PlayerMotion.TILE 한 군데만 숫자로 남겼다. WorldCollide.HALF 는 숫자가 아니라 식이다 — TILE * 0.5 - 2.0 (22 → 14). world_view.gd · main.gd · scenes/main.tscn(스폰 6168 → 4112) · measure_window · measure_camera · measure_collide · measure_move · test_project_settings · test_player_motion · test_player_scene · test_world_view · test_world_collide(손 계산 스무 개 전부 다시 계산) · redteam.sh 의 기대값. test_visible_tiles_at_48px 는 이름에서 48 을 떼고 30 × 16.875 를 강제한다 — 해상도와 타일 둘 중 아무거나 바뀌면 빨개진다.
바꾼 결정고정값에서 따라 나오는 값은 숫자로 적지 않는다. 단, 검사의 손 계산은 예외다test_world_collide.gdT 는 일부러 PlayerMotion.TILE 에서 안 가져왔다. 가져오면 타일을 바꿨을 때 기대값 스무 개가 소리 없이 틀린 채 초록이 된다. 대신 「T 와 TILE 이 어긋났나」를 test_body_is_narrower_than_a_tile 이 잰다. 게이트를 화면과 같이 키운다: 칸 수가 273 → 558 로 늘면 「색 100개 이상」 같은 문턱은 가만두는 것이 곧 반으로 헐거워지는 것이다 (100 → 200 · MAX_TILES 400 → 700). - 잰 값 (check.sh all 한 판 · 씨앗 20260914 · 창 1920×1080 · 물리 60Hz): VIEW 타일 32px · 보이는 칸 30.00 x 16.88 · 배율 2.00x · MOVE 239.72 px/s = 7.491 칸/s(240 은 안 바뀌고 칸 표기만 5 → 7.5) · COLLIDE 바다 면 7360.00 · 멈춘 자리 7345.99 = 7360 - 14 - 0.01 · 세로 169.60 px/s · 몸 반폭 14 · CAMERA 스폰 (4112, 4112) · 최대 편차 0.0000 px · DRAW 그린 칸 527(최대 558) · 불일치 0 · 최대 색차 0.8/255 · 물 47.3% → 37.7% · TESTS 66 passed, 0 failed · PARSE 26개 실패 0 · ALL GREEN(기준 6개) · SHOT 960x540 · 색 370개 · 가장 넓은 한 색 0.9% · 한 화면 색 수 217 → 349개. 대조군 한 판 REDTEAM 31 잡음, 0 놓침 · 7분 40초 (바퀴 14 는 31개 · 7분 41초). 타일을 바꾼 뒤에도 31개가 그대로 잡힌다 — 그중 셋은 이 바퀴에 문턱을 만진 것들이다 (줌 15 x 8.44 · 통째로 그리기 558 → 65536 · 캐시 문턱). 첫 판은 30 잡음, 1 놓침 이었다 — 검사를 67개로 늘려서 바닥(66)이 딱 하나만큼 헐거워졌던 것이라, 접고 다시 돌렸다
남긴 것[ASK] 계약 기준 4 의 글자가 아직 「48px 타일」이다. 무장돼 있어 세션이
채점 1 IMPORT ok · 2 PARSE 26개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 63/90줄 · 6 SHOT 960x540 색 370개 가장 넓은 한 색 0.9% → /tmp/w.png$55.5908 누적5dd0a8a 못 고친다 — 사람이 다시 무장할 때 고쳐야 한다(명령은 check.sh tests 라 채점은 맞다). 그리는 네모(48×48 = 1.5칸)가 충돌 상자(28×28)보다 넓어서 벽에 붙으면 막힌 칸에 10px 걸쳐 보인다 — 관례는 몸통 1칸 폭 · 상자는 발밑 반 칸이라 그림을 정하는 사람의 판단이다. BACKLOG P2 에 한 줄로 넘겼다. 그리기 비용이 1.83 → 3.74 ms(프레임 예산의 22%)가 된 것도 남는다 — 나무·몹·UI 가 올라오기 전에 한 번 볼 자리다.
바퀴 14

창을 띄우는 게이트를 한 프로세스로 합친다measure_viewmeasure_draw

2026-09-14 초록
문제합치고 나니 따로 돌 때는 없던 구멍이 하나 생겼다 — 한 프로세스가 반쪽만 돌고 죽으면 check.sh 가 그걸 초록으로 본다. 창 게이트를 일부러 1초에 끊어 보니 ^VIEW ^DRAW \[ 두 grep 이 둘 다 맞았다(VIEW 2줄 · DRAW [서서] 까지 찍고 죽는다). 이름도 한 번 걸렸다: 요약 줄을 WINDOW 로 찍었더니 main.gd 가 시작할 때 찍는 배너 WINDOW 1920 x 1080 과 같은 prefix 라 게이트가 침묵해도 grep 이 걸렸다
원인따로 돌 때는 게이트 하나 = 프로세스 하나라 죽으면 그 게이트가 통째로 침묵했고, 「아무것도 안 찍었다」 검사가 그걸 잡았다. 합치면 그 성질이 깨진다 — 앞 절반이 찍은 줄이 뒤 절반의 침묵을 가려 준다. 게이트를 합치는 값은 창 횟수고, 치르는 값이 이거다
고친 것tools/tests/measure_window.gd 를 새로 만들었다 — 메인 씬을 한 번 세우고 5프레임 뒤 VIEW 를 먼저 재고(DRAW 가 플레이어를 해안으로 101칸 순간이동시키므로 순서가 뒤집히면 VIEW 가 「걷다 만 화면」을 잰다), 이어서 DRAW 를 [서서]·[걷고] 두 번 잰다. 실패 수는 따로 센다VIEW ok · DRAW ok 두 줄이 예전 그대로 나온다. 끝에 WINGATE ok (VIEW 0 · DRAW 0 · 창 한 번) 한 줄을 더 찍고 tools/loop/check.sh세 줄을 다 본다. tools/tests/measure_view.gd · measure_draw.gd 와 짝 .uid 는 지웠다. 딸린 참조(measure_camera.gd · test_world_view.gd · redteam.sh 주석 · docs/GOTCHAS.md)도 새 이름으로 고쳤다
바꾼 결정게이트를 합칠 때는 「둘 다 돌았다」를 찍는 줄을 같이 만든다. 합치기는 공짜가 아니다 — 프로세스 경계가 하던 일(죽으면 침묵한다)을 줄 하나로 대신 사야 한다
남긴 것뺏김이 0 이 되지는 않는다. 계약 한 판에 창은 아직 2번 뜬다 — 남은 하나는
잰 값(씨앗 20260914 · 창 1920x1080 · 논리 960x540 · 줌 1 · macOS 25.6 · 표본 간격 100ms) check.sh tests 한 판 표본 102 · 뺏김 9(바퀴 13: 110 · 14) · 계약 한 판 166 · 12 (183 · 13) · 둘 다 시작 앞 = 끝 앞 iTerm2 · 창 여는 횟수 tests 2 → 1 · 계약 3 → 2 · 게이트 자체 VIEW 0.58s + DRAW 1.25s = 1.83s → 1.29s · 계약 한 판 21.86s → 20.28s · ALL GREEN(기준 6개) · TESTS 66 passed, 0 failed · PARSE 26개 스크립트, 실패 0. 잰 값은 글자 하나 안 달라졌다: VIEW 논리 960x540 · 창 1920x1080 · 배율 2.00x · 보이는 칸 20.00 x 11.25 · DRAW [서서] 표본 7844 · 불일치 0 · 물 45.9% · 그린 칸 273 · DRAW [걷고 144 px] 표본 7844 · 불일치 0 · 물 31.0%. 대조군 7/7 (게이트 단위 · 손 안 댄 판은 초록): 줌 → 보이는 칸 10.00 x 5.62 · 창 축소 → 창 1600x900 · 배율 1.00x · 안 그림 → 7844/7844 · 다른 씨앗 → 7844/7844 · 통째로 → 65536 칸 · 캐시 안 버림 → [서서] 부터 7844/7844 · 두 칸 문턱 → [걷고]6982/7844. 창 축소 대조군이 VIEW FAIL 2개 인데 DRAW ok 로 끝난다 — 앞 절반이 빨개도 뒤 절반이 끝까지 돈다는 증거다. 잘린 판 대조군(1초에 끊기)은 WINGATE FAIL 게이트가 끝까지 못 갔다 로 잡혔다 — 그 줄이 없었으면 초록이었다 계약 단위 한 판도 커밋 뒤에 돌렸다(바퀴 11 이 남긴 숙제다): REDTEAM 31 잡음, 0 놓침 · 7분 41초 — 바퀴 10 의 30개 · 10분 03초 · 1 놓침에서 줄었다. 그 한 판의 창은 93번 → 62번이다
채점 1 IMPORT ok · 2 PARSE 26개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 70/90줄 · 6 SHOT 960x540 색 217개 가장 넓은 한 색 1.3% → /tmp/w.png$44.4715 누적acec96f 기준 6 의 shot.sh 다. 그걸 또 어디에 합칠지는 사람이 정할 것이다(게이트가 아니라 산출물을 굽는 명령이라 성격이 다르다). redteam.sh 한 판의 창은 93번 → 62번이다 ---
바퀴 13

창을 띄우는 게이트가 포커스를 안 뺏는다measure_view · measure_draw ·

2026-09-14 초록
문제막을 수 없다. 항목이 시킨 WINDOW_FLAG_NO_FOCUS + 화면 밖 위치를 포함해 6가지를 재서 전부 뺏겼다. 창을 띄우는 순간 Godot 이 맨 앞 앱이 된다
원인no_focus이 키 윈도우가 되는 것만 막는다 — 앱의 활성화는 못 막는다. 창 생성 시점에 걸어도(project.godot) 그대로였고, LSUIElement 래퍼 번들도 LaunchServices 의 open -g 도 엔진의 활성화 호출을 못 이겼다. 화면 밖 위치는 macOS 가 화면 안으로 물린다(요청 5000,5000 → 실제 1536,1154 = 화면-창). CLI 로 설정을 덮는 길(--display/window/size/no_focus=true)은 조용히 무시된다 — 인자 목록에는 남는데 설정값은 false 였다
고친 것새 도구 tools/loop/focus.shlsappinfo 로 맨 앞 앱을 계속 샘플해 표본 수 · 뺏긴 표본 수 · 앞뒤의 맨 앞 앱을 찍는다(접근성 권한이 필요 없다). 못 막으니 되돌려 준다: tools/loop/godot.shNARU_FOCUS_RESTORE=1 일 때 띄우기 직전의 맨 앞 앱을 기억했다가 끝나고 open 으로 돌려보낸다. 창을 띄우는 두 곳에만 걸었다 — tools/loop/check.sh 의 VIEW · DRAW 와 tools/loop/shot.sh. docs/NUMBERS.md 11절 · docs/GOTCHAS.md macOS 3줄
바꾼 결정막을 수 없는 것은 되돌리거나 횟수를 줄인다. 그리고 project.godotno_focus 를 안 남겼다 — 켜두면 아무 이득 없이 사람이 직접 띄운 창에서 WASD 가 안 먹는다. 게이트 편의가 게임 동작을 바꾸면 안 된다
남긴 것뺏기는 것 자체는 그대로다 — 창 두 번이 도는 ~1.8초 동안 타이핑은 여전히
잰 값대조군 헤드리스 표본 6 · 뺏김 0 / 창 표본 7 · 뺏김 4 — 도구는 창을 본다. 막기 6종 전부 실패 — ① 실행 중 플래그 4/7 ② 창 생성 시점 no_focus 4/7 ③ 화면 밖 4/7 ④ LSUIElement 3/7 ⑤ open -g -n 5/13 ⑥ 최소화 18/23 + 프레임버퍼가 색 1개(죽는다. 손 안 댄 같은 프레임은 색 202개). 되돌리기 — 끔 Finder → SystemUIServer(안 돌아옴) · 켬 3/3 Finder → Finder · 헤드리스에 켜도 오발 없음. check.sh tests 한 판 표본 110 · 뺏김 14 · 시작 앞 = 끝 앞 · 계약 한 판(창 3번) 표본 183 · 뺏김 13 · 시작 앞 = 끝 앞 · TESTS 66 passed, 0 failed · VIEW 배율 2.00x · 보이는 칸 20.00 x 11.25 · DRAW [서서]/[걷고 144 px] 표본 7844 · 불일치 0 · SHOT 960x540 색 217개 · 한 색 1.3% · ALL GREEN(기준 6개 · 21.86s). 새 게임 코드는 없다 — 도구만 건드린 바퀴다
채점 1 IMPORT ok · 2 PARSE 27개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 74/90줄 · 6 SHOT 960x540 색 217개 가장 넓은 한 색 1.3% → /tmp/w.png$40.9262 누적5174b4d Godot 으로 간다. 없어진 건 「끝나고도 안 돌아온다」쪽이다. 남은 길은 횟수 줄이기 — VIEW 와 DRAW 를 한 프로세스로 합치면 한 판 2번 → 1번, redteam.sh 62번 → 31번. BACKLOG 에 한 줄로 넣었다. 되돌리기는 게이트만 켠다 — 사람이 직접 띄운 창은 보는 동안 딴 앱으로 옮겼을 수 있어서 도로 뺏으면 그게 또 도둑질이다
바퀴 12

계약이 check.sh tests 를 두 번 돈다 — 기준 3(mintests.sh)과 기준 4 가

2026-09-14 초록
문제세션이 일지를 안 적었다 — 이 절은 사람이 커밋과 실측으로 복원한 것이다. 원래 문제: 계약 기준 3(mintests.sh)과 기준 4 가 같은 실측 7종을 각각 돌았다. 한 판이 두 배로 길고 흔들릴 기회도 두 배였고, 창이 한 판에 4번 떠서 일하는 사람의 포커스를 뺏었다.
원인기준 3 은 「개수가 줄지 않는다」만 물으면 되는데 mintests.shcheck.sh tests 를 불렀다. tests 는 단위 검사 뒤에 실측 게이트 7종을 달고 있다 — 물어본 적 없는 것까지 같이 딸려 왔다.
고친 것tools/loop/check.shunit 단계를 새로 만들고 mintests.sh 가 그걸 부른다. 무장된 .loop/criteria.tsv 는 안 건드렸다 — 세션의 red line 이다. 계약 글자는 그대로 두고 그 글자가 부르는 스크립트 쪽에서 갈랐다. 실측이 깨지면 기준 4 가 여전히 빨개지므로 계약이 잡는 범위는 그대로다. (커밋 2bb1686)
바꾼 결정없음
남긴 것일지 게이트가 처음으로 진짜 일을 했다. 세션이 일을 하고 커밋까지 했는데 BACKLOG 체크와 일지를 둘 다 빼먹었고, 드라이버가 초록인데도 멈췄다 — 안 멈췄으면 「무슨 일이 있었는지 아무도 모르는 초록 바퀴」가 그대로 푸시됐을 것이다. 다만 왜 빼먹었는지는 모른다. 5분 · $1.55 로 가장 짧고 싼 바퀴였다 — 짧은 바퀴가 마무리를 건너뛰는 경향이 있는지 몇 판 더 보고 판단한다.
잰 값계약 한 판 32.81s → 22.0s · 창 뜨는 횟수 4번 → 2번 · ALL GREEN(기준 6개) · TESTS 66 passed, 0 failed · PARSE 27개 실패 0
채점 1 IMPORT ok · 2 PARSE 27개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 88/90줄 · 6 SHOT 960x540 색 217개 가장 넓은 한 색 1.3% → /tmp/w.png$36.3796 누적2bb1686
바퀴 11

measure_facing.gd 가 사람의 마우스에 안 흔들리게 한다 — 진짜 커서를 뺏어 재는

2026-09-14 초록
문제게이트가 Input.warp_mouse진짜 커서를 뺏어 쟀다. 사람이(또는 아무 프로세스가) 마우스를 건드리면 튄다 — 바퀴 8 · 사람 세션 · 바퀴 9 · 바퀴 10 네 번. 재현을 못 해서 네 바퀴 동안 「사람 탓」으로만 적고 넘겼다.
원인루트 뷰포트의 get_mouse_position() 은 매번 OS 커서를 되묻는다. 그래서 warp 해 놓은 값이 사람의 손에 언제든 덮인다 — 게이트가 기계 전체와 커서 하나를 놓고 경합하고 있었다.
고친 것tools/tests/measure_facing.gd — 메인 씬을 루트가 아니라 SubViewport 세우고 합성 InputEventMouseMotionpush_input 으로 밀어 넣는다. SubViewport 는 밀어 넣은 이벤트만 본다(warp_mouse 를 해도 안 흔들린다 — 쟀다). 창이 필요 없어져 tools/loop/check.sh--headless 로 부른다. tools/loop/redteam.shFACE 대조군 ④ 캔버스 변환을 넣었다(30 → 31개).
바꾼 결정재현할 수 없는 흔들림은 방해를 직접 만들어서 재현한다. 두 번째 Godot 을 띄워 매 프레임 커서를 휘젓는 방해 프로세스를 썼더니 네 바퀴짜리 흔들림이 한 판에 재현됐다(옛 게이트 FAIL 13개 · 7구간 전부). 「사람 탓」은 원인이 아니라 아직 안 겨눈 조건이었다. 그리고 그 방해가 숨어 있던 둘째 버그를 끌어냈다 — 게이트는 facing 을 정하는 _physics_process 를 기다리면서 유휴 프레임을 세고 있었다. 부하가 걸리면 유휴 4프레임이 물리 한 틱도 못 품는다. Engine.get_physics_frames()물리 틱을 센다(SETTLE_TICKS = 2).
남긴 것redteam.sh 한 판은 여전히 10분이다 — 줄어든 것은 「그동안 마우스를 못
잰 값방해를 건 채 — 옛 게이트 FAIL 13개 · 7구간 전부(커서 각 -155.5°·-137.3°· -158.2°, 기대 0°·90°·180°), 새 게이트 헤드리스 7구간 전부 ok · 오차 0 (0.0°·90.0°·180.0°·-90.0°·46.0°·60.0°) · 창 띄운 채 15/15 초록. 둘째 버그: 마우스 안 건드리고 부하만 — 유휴 프레임 셀 때 12회 중 1회 빨강 (커서 0.0° 인데 방향 (-1,0), 기대 (1,0)) → 물리 틱 셀 때 15회 중 0회. FACE 대조군 4종 전부 잡음 — ① 커서 안 읽음 FAIL 4개 · ② 히스테리시스 건너뜀 FAIL 1개 · ③ 코 안 돎 FAIL 4개 · ④ 캔버스 변환 빼먹음 FAIL 6개(새로 넣었다). TESTS 66 passed, 0 failed · PARSE 27개 실패 0 · IMPORT ok · ALL GREEN(기준 6개 · 32.81s). 새 게임 코드는 없다 — 게이트만 건드린 바퀴다.
채점 1 IMPORT ok · 2 PARSE 27개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 88/90줄 · 6 SHOT 960x540 색 217개 가장 넓은 한 색 1.3% → /tmp/w.png$34.8262 누적bcf276e 쓴다」쪽이다(이제 check.sh tests 어디에서도 warp_mouse 를 안 부른다. 창을 띄우는 게이트는 measure_view.gd · measure_draw.gd 둘뿐이고 커서는 안 건드린다). 길이 자체는 BACKLOG 의 「계약이 check.sh tests 를 두 번 돈다」가 남아 있다. 사람이 정할 것: 이번 바퀴에 새 대조군을 게이트 단위로만 돌려 4/4 를 확인했다 — 계약 단위 redteam.sh 한 판(10분)은 커밋 뒤에 돌린다.
사람

가짜 회귀로 멈췄다 · FACE 게이트가 사람 마우스에 다섯 번째

2026-09-14 초록
문제바퀴 10 이 초록으로 일하고 커밋까지 했는데 드라이버가 「회귀 — 기준 3」 으로 멈췄다. 그런데 채점은 ALL GREEN 이었고, 남은 증거는 TESTS 66 passed, 0 failed 인데 빨강 이라는 모순이었다.
원인셋이 겹쳤다. ① 항목의 verify 가 redteam.sh 였다. 그게 계약을 30번 부르면서 .loop/results.json자기 마지막 판으로 덮었고, 회귀 감지가 「계약이 깬 것」이 아니라 「대조군이 일부러 깬 것」을 보고 멈췄다. ② mintests.sh 의 grep 이 ^TESTS 만 남겨서 실측 게이트가 실패한 줄을 버렸다 — 그래서 증거가 모순으로 보였다. ③ 진짜 빨강은 FACE 였다 — measure_facing.gd 가 진짜 커서를 뺏어 재는데 사람이 그때 마우스를 쓰고 있었다.
고친 것드라이버가 채점 직후의 results.json 을 따로 떠 두고 그것과 회귀를 비교한다. verify 뒤에는 증거를 채점 시점으로 되돌린다 — 일지의 「채점」과 대시보드가 대조군의 빨강을 물려받으면 안 된다. mintests.sh 는 빨갈 때 거른 줄을 도로 보여준다 — 고치자마자 다음 판에서 FACE FAIL 커서 각 60°-놓음 — 잰 값 61.28° · 기대 60.00° 를 한 줄로 잡아냈다. 그리고 measure_facing.gd 항목을 P1 맨 앞으로 올렸다.
바꾼 결정FACE 를 먼저 고친다. 매 바퀴 계약이 이 게이트를 돌기 때문에, 안 고치면 사람이 자기 컴퓨터를 쓰는 것만으로 아무 바퀴나 랜덤하게 빨개진다. 「사람이 마우스를 안 만지면 된다」는 해결이 아니다.
남긴 것기준 3 과 4 가 같은 check.sh tests 를 각각 돈다. 실측 게이트가 7종이라 한 판이 두 배로 길고 흔들릴 기회도 두 배다 — 백로그에 올렸다. 그리고 이번 판이 다시 알려준 것: 증거를 거르는 검사는 빨개져도 쓸모가 적다. mintests.sh 를 고치자마자 다섯 번 못 찾던 원인이 한 줄로 나왔다.
잰 값대조군 두 판 — REDTEAM 29 잡음, 1 놓침 · 28 잡음, 2 놓침. 놓친 판 전부 FACE FAIL (-1.43°·92.54°·61.28°, 기대 ·90°·60°). 손 안 댄 check.sh tests 는 exit 0 · TESTS 66 passed, 0 failed · DRAW 표본 7844 · 불일치 0 · CAMERA 최대 편차 0.0000 px. 누적 다섯 번 — 바퀴 8 · 사람 세션 · 바퀴 9 · 대조군 두 판.
채점 1 IMPORT ok · 2 PARSE 27개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 85/90줄 · 6 SHOT 960x540 색 217개 가장 넓은 한 색 1.3%$30.9994 누적 (사람 세션 — 루프 예산 밖)0b56f70
바퀴 10

measure_draw.gd걷는 구간에 대조군을 붙이거나 그 구간을 뺀다

2026-09-14 초록
문제바퀴 9 가 넘긴 그대로다. 게이트는 [서서]·[걷고] 두 번 재는데 걷는 구간이 혼자 잡는 것이 하나도 없었다 — 대조군 4종이 전부 [서서] 에서 먼저 빨개진다. 혼자 잡는 게 없으면 그 구간은 게이트가 아니라 장식이고, 언젠가 「느리다」는 이유로 잘린다.
원인게이트가 플레이어를 해안까지 순간이동시킨다(스폰 128칸 → 해안 229칸 = 101칸). 그 점프 자체가 보이는 범위를 통째로 갈아치워서 어떤 캐시 버그든 [서서] 에서 이미 터진다. 바퀴 9 는 「캐시를 망가뜨리는 한 줄」만 찾았지 「순간이동은 멀쩡히 넘기고 한 칸씩 걸을 때만 망가지는 한 줄」을 못 찾았다 — 필요한 것은 버그가 아니라 문턱이었다.
고친 것tools/loop/redteam.sh 에 대조군 ⑤ 를 넣었다 — main.gd 의 캐시 조건을 r != _cache_range 에서 「두 칸 넘게 움직였을 때만 다시 채운다」로 바꾼다. 순간이동(101칸)은 문턱을 넘으니 멀쩡히 채우고, 걸을 때는 한 칸씩 움직여서 캐시가 한 칸 뒤처진 채로 끝난다. tools/tests/measure_draw.gd 머리말의 「걷는 구간이 혼자 잡는 것은 아직 하나도 없다」도 지웠다 — 이제 거짓이다.
바꾼 결정대조군은 「기능을 망가뜨리는 것」만이 아니다. 게이트의 어떤 구간이 무엇을 잡는지 물으려면, 망가지는 *조건*을 겨눠야 한다. 여기서 겨눈 것은 캐시가 아니라 캐시를 버리는 문턱이고, 그래서 순간이동과 걷기를 갈라낼 수 있었다. - 잰 값 (씨앗 20260914 · 창 1920x1080 · 논리 960x540 · 줌 1 · 60Hz): 대조군 ⑤ 는 [서서] 표본 7844 · 불일치 0 · 최대 색차 0.8/255 · 채운 횟수 2 (손 안 댄 상태와 글자 하나 안 다르다) · [걷고 144 px] 불일치 6982 / 7844 · 최대 색차 104.0/255 · 채운 횟수 5 → 3 · 첫 어긋남은 칸 (216,122) 71884b(기대 647b3e). 단위 검사는 TESTS 66 passed, 0 failed전부 초록. 계약 ALL GREEN(기준 6개 · 33.65s). 전체 REDTEAM 29 잡음, 1 놓침(10분 03초 · 대조군 30개).
남긴 것놓친 하나는 FACE — 그것도 아무것도 안 깨뜨린 첫 칸
채점 1 IMPORT ok · 2 ✘ FAIL ./scripts/_redteam.gd / PARSE 28개 스크립트, 실패 1 · 3 TESTS 66 passed, 0 failed · 4 ✘ FACE FAIL 커서 각 아래 — 잰 값 87.15° · 기대 90.00° / FACE FAIL 커서 각 왼쪽 — 잰 값 177.60° · 기대 180.00° / FACE FAIL 커서 각 위 — 잰 값 -88.26° · 기대 -90.00° / FACE FAIL 커서 각 46°-붙잡음 — 잰 값 48.12° · 기대 46.00° / FACE FAIL 커서 각 60°-놓음 — 잰 값 61.74° · 기대 60.00° / FACE FAIL 6개 (구간 7 · 거리 200 px · 스냅 45° · 히스테리시스 10°) · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 85/90줄 · 6 SHOT 960x540 색 217개 가장 넓은 한 색 1.3% → /tmp/w.png$30.9994 누적a409c6a (「손 안 댄 상태는 초록이다」)에서 -6.73°·47.61°·61.32°(기대 0°·46°·60°). 마지막 칸 「원복하면 다시 초록이다」는 초록이라 게이트가 아니라 사람의 마우스다. 바퀴 8 · 사람 세션 · 바퀴 9 · 바퀴 10 — 네 번째다. 백로그의 다음 항목이 그거다. ---
바퀴 9

월드를 화면에 그린다 — 보이는 칸만. main.gd 의 임시 격자를 걷어낸다

2026-09-14 초록
문제두 가지다. ① 한 칸의 색이 6.71 µs 였다 — 잡음을 두 번 도는데(tile_at 이 한 번, height_at 이 한 번) 273칸이면 1.83 ms · 60Hz 한 프레임 예산의 11% 다. 나무도 몹도 UI 도 아직 없는데 그걸 프레임마다 쓸 수는 없었다. ② 캐시를 넣고 나서 새 게이트의 걷는 구간이 대조군을 하나도 못 잡는 것을 발견했다 — 대조군 4종이 전부 앞 구간([서서])에서 이미 빨개졌다.
원인① 색을 정직하게 만들려다 잡음을 두 번 돌았다. h > SEA_LEVEL 을 색 쪽에서 다시 쓰면 싸지지만, 그러면 판정이 두 벌이 되어 언젠가 한쪽만 고쳐지고 「보이는 땅에 못 서는」 날이 온다. 그래서 비용을 받아들이고 캐시로 갚기로 했다. ② 게이트가 해안까지 순간이동시키는데, 그 순간이동 자체가 카메라를 옮겨 캐시를 버리게 만든다. 즉 캐시가 상했는지는 걷기 전에 이미 드러난다. 「걷고 나서 또 잰다」는 그럴듯한 이유가 있었지 증거가 있었던 게 아니다.
고친 것scripts/world_view.gd 를 새로 만들었다 — 「어느 칸을 그리나」 + 「그 칸이 무슨 색인가」 둘뿐인 순수 계산이다. 물·땅 분기는 WorldGen.tile_at 이 한다 (충돌이 묻는 것과 글자 그대로 같은 함수). scripts/main.gd_drawvisible_world_rect() 범위만 그리고, 보이는 범위가 바뀔 때만 색을 다시 채운다. scripts/world_gen.gd_unitunit 으로 열었다 — 흔들기가 같은 해시를 쓴다. 새 실측 게이트 tools/tests/measure_draw.gdcheck.sh testsCAMERA 뒤에 넣었다.
바꾼 결정화면은 이제 증거다. 전에는 「그려 봤더니 그럴듯하더라」였는데, 이제 구운 픽셀 7844 점을 그 자리의 월드 칸 색과 맞춰 본다 — 틀리면 어느 픽셀이 어느 칸에서 무슨 색이었는지가 찍힌다. 그리고 칸마다 밝기를 흔드는 것은 꾸밈이 아니라 게이트다: 안 흔들면 스폰 한 화면이 거의 단색이라 shot.sh --max-flat 이 아무것도 안 잡는다.
남긴 것놓친 하나는 게이트가 아니라 사람의 마우스다 — 「원복하면 초록」에서
잰 값TESTS 66 passed, 0 failed(57 → 66) · PARSE 27개 실패 0 · IMPORT ok · ALL GREEN(기준 5개). 실측(씨앗 20260914 · 창 1920x1080 · 논리 960x540 · 줌 1 · 60Hz): DRAW [서서] 표본 7844 · 불일치 0 · 최대 색차 0.8/255 · 물 45.9% · 땅 54.1% · 그린 칸 273 · 채운 횟수 2 · DRAW [걷고 144 px] ... 물 31.0% · 땅 69.0% · 채운 횟수 5 · 해안 칸 (229,128) · 보이는 칸 273 / 월드 65536 = 240배 · 한 칸 6.71 µs · 273칸 1.83 ms · SHOT 960x540 색 217개 · 가장 넓은 한 색 1.3%(임시 격자는 3개 · 50.0%). 대조군 4종(안 그림 / 다른 씨앗 / 통째로 그림 / 캐시 안 버림)은 넷 다 단위 검사 66개를 초록으로 남긴 채 실측만 잡았다 — 75.4/255 · 121.3/255 · 픽셀은 전부 일치하고 그린 칸만 273 → 65536 · 86.0/255. 전체 REDTEAM 28 잡음, 1 놓침.
채점 1 IMPORT ok · 2 PARSE 27개 스크립트, 실패 0 · 3 TESTS 66 passed, 0 failed · 4 TESTS 66 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 85/90줄$28.3357 누적388b80f FACE91.73°·-92.55°(기대 90°·-90°)로 튀었다. measure_facing.gd 는 진짜 커서를 뺏어 재므로 사람이 마우스를 건드리면 빨개진다. 곧바로 다시 돌린 계약은 ALL GREEN 이고, 바퀴 8 과 같은 자리에서 두 번째다 — 세 번째가 오면 게이트를 고쳐야 한다. measure_draw.gd 의 걷는 구간은 대조군 없이 남았다. BACKLOG 에 「대조군을 붙이거나 그 구간을 뺀다」로 넘겼다 — 지금 그 구간이 더하는 것은 대조군이 아니라 표본이다 (물리가 민 진짜 재충전 3번, 물 45.9% → 31.0% 인 다른 화면). 사람이 정할 것: 색이다. SHORE·GRASS·DEEP·SHOAL 네 값과 흔들기 폭 0.035 는 눈으로 고른 값이고, GDD E-1 은 아트를 「컨셉이 다 잡힌 뒤」로 미뤄 뒀다. 지금 잰 것은 「화면이 월드와 맞나」이지 「예쁜가」가 아니다.
사람

대조군 한 판이 빨갰는데 재현이 안 됐다

2026-09-14 초록
문제P1 을 다 돌린 뒤 redteam.sh 를 돌렸더니 REDTEAM 24 잡음, 1 놓침 이 나왔다. 놓친 것이 하필 마지막 「원복하면 다시 초록이다」(기대 exit 0 · 잰 값 1) 였다 — 대조군이 헐거운 게 아니라 원복 뒤에도 빨갛다는 뜻이라 훨씬 나쁜 신호였다. 그런데 바로 잰 워킹트리는 깨끗했고 계약은 ALL GREEN 이었다.
원인모른다. 재현이 안 됐다. expect() 가 계약 출력을 >/dev/null 2>&1 로 통째로 버려서 무엇이 왜 빨갰는지 증거가 하나도 안 남았다. 그 사이 내가 계약을 다시 돌려 .loop/results.json 까지 덮었다. 실측 게이트(MOVE·VIEW·FACE·CAMERA·COLLIDE)는 진짜 창을 띄우므로 Godot 을 25번 연달아 띄운 끝에 한 번 흔들렸을 수 있지만 증거가 없다.
고친 것redteam.shexpect()놓친 판의 증거를 남긴다 — 빨간 기준과 증거를 그 자리에서 찍고, 계약 출력 · results.json · 워킹트리 상태를 .loop/redteam/<번호>-* 로 떨군다. 다음에 같은 일이 나면 10분을 다시 태우지 않아도 된다.
바꾼 결정없음
남긴 것원인은 바퀴 9 가 찾았다 — 게이트가 아니라 사람의 마우스다. measure_facing.gd진짜 커서를 뺏어 재므로 사람이 그 사이 마우스를 건드리면 FACE 가 기대 90° 에서 91.73° 로 튄다. 나는 그때 이 기계를 쓰고 있었다. 같은 자리(「원복하면 초록」)에서 바퀴 8 · 이 판 · 바퀴 9 — 세 번째다. 세 번째가 오면 고치기로 했으므로 BACKLOG P1 에 올렸다. 그리고 이 한 판이 알려준 게 하나 있다: 증거를 안 남기는 검사는 빨개져도 쓸모가 적다. expect() 말고도 출력을 버리는 자리가 더 있는지 볼 만하다.
잰 값재현 시도 — check.sh tests 3회 연속 TESTS 57 passed, 0 failed · MOVE 가로 239.14 / 239.48 / 239.46 대각 240.00 / 238.72 / 239.88 (기대 240 ±5) · CAMERA 최대 편차 0.0000 px (허용 0.50) · COLLIDE 해안 미끄럼 세로 171.11 / 169.61 / 169.56 px/s · FACE 46°-붙잡음 / 60°-놓음 셋 다 동일. 흔들리는 값은 못 찾았다. 다시 돌린 대조군 — REDTEAM 25 잡음, 0 놓침.
채점 1 IMPORT ok · 2 PARSE 25개 스크립트, 실패 0 · 3 TESTS 57 passed, 0 failed · 4 TESTS 57 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 62/90줄$22.1329 누적 (사람 세션 — 루프 예산 밖)511db34
바퀴 8

카메라가 플레이어를 따라간다. 줌 없음 (해상도가 시야 이득이 되면 안 된다)

2026-09-14 초록
문제카메라를 붙이자 커서 게이트(FACE)가 통째로 무너질 뻔했다. measure_facing.gd 는 「월드 좌표 × 최종 배율 = 창 좌표」로 진짜 커서를 옮긴다. 플레이어가 월드 (6168, 6168) 로 가면 그 곱은 창 밖 12336 px 이라 커서가 아예 안 간다. 그리고 바퀴 4 에 넣어 둔 줌 대조군이 아무것도 안 잡는 가짜가 됐다.
원인둘 다 「화면 좌표 = 월드 좌표」라는 죽은 전제 위에 서 있었다. 카메라가 그 둘을 갈랐다. 대조군 쪽은 한 겹 더 있다 — Godot 의 Camera2D먼저 트리에 들어온 쪽이 화면을 잡는다. 진짜 카메라가 생긴 뒤로는 main.tscn 에 카메라를 덧붙여도 그냥 무시된다.
고친 것scenes/player.tscnCamera2D(줌 1 · 부드럽게 따라가기 끔) — 코드가 매 프레임 따라 붙이지 않는다. 부모-자식이라 공짜로 따라가고 한 틱 뒤처질 자리가 없다. scripts/main.gd 에서 tile_offsetscreen_of() 를 지우고 visible_world_rect() 를 뒀다 (임시 격자를 월드에 고정으로 바꿨다 — 화면에 고정돼 있으면 카메라가 따라가는지 사람 눈에 안 보인다). scripts/world_collide.gd 의 이동량 인자도 뺐다. tools/tests/measure_facing.gd 는 이제 캔버스 변환으로 화면에 찍고 나서 커서를 옮긴다. 새 게이트 tools/tests/measure_camera.gd + 단위 검사 tools/tests/test_camera.gd(5개). tools/loop/redteam.sh 의 줌 대조군은 진짜 카메라의 줌을 직접 걸도록 겨눌 곳을 옮겼다.
바꾼 결정화면 칸 = 월드 칸. 바퀴 7 의 tile_offset 은 0 이 되는 게 아니라 사라졌다. 그리고 카메라 따라가기는 코드가 아니라 씬 구조로 한다 (NUMBERS 8절).
남긴 것놓친 하나는 여전히 테스트 바닥이다(검사 57 · mintests 바닥 53) — 무장된
잰 값CAMERA 최대 편차 0.0000 px · 걸은 거리 484.00 px · 줌 1.00x · 보이는 칸 20.00 x 11.25 · 표본 294(씨앗 20260914 · 헤드리스 · 60Hz · 4구간 × 0.5s) · 스폰 월드 (6168,6168) → 화면 (480,270) · TESTS 57 passed, 0 failed(53 → 57) · VIEW 배율 2.00x · 카메라 1.00x · COLLIDE 바다 면 11040.00 → 멈춘 자리 11017.99 = 11040 - 22 - 0.01 · 세로 169.70 px/s(좌표계가 옮겨간 값) · SHOT 960x540 색 4개 가장 넓은 한 색 50.0% · ALL GREEN(기준 5개) · 대조군 REDTEAM 24 잡음, 1 놓침 — 새 3종은 단위 57개를 전부 초록으로 남긴 채 실측만 잡았다: 카메라 끔 8363.56 px · 실행 중 줌 보이는 칸 10.00 x 5.62 · 부드럽게 끌려옴 82.10 px
채점 1 IMPORT ok · 2 PARSE 24개 스크립트, 실패 0 · 3 TESTS 57 passed, 0 failed · 4 TESTS 57 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 78/90줄$22.1329 누적620f65d criteria.tsv 안이라 사람이 올린다. 아직 아무도 걸어 보지 않았다: 편차 0 은 「따라간다」를 증명하지만 따라가는 느낌이 좋은지는 사람 눈이다(죽은 구역 없이 딱 붙는 카메라가 4방향 이동에서 멀미를 부를 수 있다). 해안은 101칸 밖이라 여전히 화면에 안 보인다 — 다음 항목.
바퀴 7

이동 충돌 — 바다에 못 들어가고, 해안에 비스듬히 붙으면 미끄러진다

2026-09-14 초록
문제충돌을 어느 좌표계에 붙일지가 막혔다. 섬의 스폰은 월드 한가운데(128,128)인데 플레이어는 화면 원점 근처(504,264 = 화면 칸 10,5)에 있다. 그대로 월드를 꽂으면 시작 칸이 테두리 바다라 플레이어가 첫 프레임부터 얼어붙는다. 그렇다고 플레이어를 스폰 칸으로 옮기면 화면(960×540) 밖으로 나가 오렌지 네모가 사라진다 — 카메라는 다음 항목이고, 검사(test_player_scene.gd)가 「시작 위치가 화면 안」을 지킨다. 중간에 measure_facing 이 빨개져서 한참 내 코드를 의심했는데 HEAD 에서도 빨갰다.
원인카메라가 없는 동안은 화면 좌표계와 월드 좌표계가 같다고 가정할 수 없다. 백로그가 충돌을 카메라보다 앞에 둔 이상 이 어긋남은 이번 바퀴가 떠안아야 하는 것이었다. measure_facing 쪽은 내 변경이 아니라 사람의 마우스였다 — 진짜 커서를 뺏는 게이트라 손이 닿으면 커서 각부터 어긋난다. 마우스에서 손을 떼니 세 번 연속 초록이었다.
고친 것scripts/world_collide.gd 를 새로 만들었다 — 격자에 대고 축을 따로 푸는 순수 계산(move·overlaps·solid_from_seed). player.gdmove_and_slide 를 버리고 이걸 부른다. main.gd 에 씨앗(20260914)과 화면 칸 → 월드 칸 이동량 tile_offset(118,123) 을 두고 플레이어에 solid Callable 을 꽂는다 — 카메라가 오면 0 이 되어 사라지는 값이다. 검사 18개(test_world_collide.gd), 실측 게이트 tools/tests/measure_collide.gd, check.sh testsCOLLIDE 단계, 대조군 3종. GOTCHAS 에 두 줄(_ready 타이밍 · 마우스).
바꾼 결정이동을 물리 엔진에 안 맡긴다. 65536칸에 정적 몸을 세울 수 없고, 창 주변만 세우면 결과가 「지금 무엇이 세워져 있나」에 달린다. 같은 입력이 같은 자리를 줘야 한다(GDD D-1 서버 재현성 · C-4 원정 보고 검증). 미끄러짐은 축을 따로 푸는 데서 공짜로 나온다.
남긴 것놓친 둘은 게이트가 아니라 환경과 바닥이다 — 「검사를 지우면 바닥이 잡는다」
잰 값씨앗 20260914 · 헤드리스 · 물리 60 Hz · 몸 반폭 22 · 틈 0.01 — TESTS 53 passed, 0 failed(35 → 53) · PARSE 21개 실패 0 · IMPORT ok · MOVE 가로 239.07 / 대각 239.99 px/s · VIEW 보이는 칸 20.00 x 11.25 · COLLIDE 해안 월드칸 (229,122) · 바다 면 5376.00 px · 바다로 직진 x 5353.99(= 5376 - 22 - 0.01) · 세로 0.00 px/s · 해안 미끄럼 x 5353.99 · 세로 169.64 px/s(기대 240/√2 = 169.71 ±5) · 질의 100000회 317.8 ms (1회 3.178 µs · 막힘 67.7%) · ALL GREEN(기준 5개) · REDTEAM 20 잡음, 2 놓침 — 새 대조군 3종은 전부 잡았고, 앞의 둘은 단위 검사 53개를 초록으로 남긴 채 COLLIDE 잡았다.
채점 1 IMPORT ok · 2 PARSE 22개 스크립트, 실패 0 · 3 TESTS 53 passed, 0 failed · 4 TESTS 53 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 74/90줄$17.3197 누적4490f4a (검사 53 · mintests 바닥 35, 무장된 criteria.tsv 안이라 안 건드렸다)와 「원복하면 다시 초록이다」(FACE 가 마우스로 한 번 튐. 같은 트리에서 다시 돌린 계약은 ALL GREEN). 사람이 정할 것: 몸 반폭 22 는 「폭 1칸 통로를 지난다」로 고른 값이라 손맛은 걸어 봐야 안다. 그리고 아직 해안이 눈에 안 보인다 — 스폰에서 가장 가까운 곧은 해안이 101칸 밖이고 화면에는 임시 격자만 있다. 카메라(P1-6)와 월드 그리기까지 가야 사람이 「부딪히는 것」을 본다. ---
바퀴 6

시드 기반 월드 생성 256×256, 땅/바다 2종, 같은 시드 = 같은 월드

2026-09-14 초록
문제첫 구현이 섬이 아니라 동그란 덩어리였다. 숫자는 전부 맞았다 — 테두리는 물, 땅 33~37%, 체크섬도 재현됐다. 그런데 PNG 를 구워 보니 씨앗을 바꿔도 거의 같은 원이었다. 「같은 시드 = 같은 월드」는 통과하는데 「다른 시드 = 다른 월드」가 눈으로는 거짓이었다.
원인잡음의 진폭이 감쇠보다 작았다. fBm 4옥타브는 평균 0.5 언저리에 몰려 있어서 폭이 ±0.15 쯤인데, 가장자리 감쇠는 0 → 1.25 로 훑는다. 해안선의 위치를 잡음이 아니라 거리가 정한다 — 그래서 반지름이 고정된 원이 나온다.
고친 것scripts/world_gen.gdCONTRAST 2.5 를 넣어 잡음을 평균에서 벌렸다 ((n-0.5)*2.5+0.5 를 0..1 로 자른다). 덩어리 크기도 BASE_CELLS 6.0 → 3.5 로 키웠다. 부등식 두 줄은 그대로 산다 — 자른 값이 여전히 0..1 이라 테두리 물 · 한가운데 땅이 깨지지 않는다. 그리고 이걸 보려고 tools/qa/world_png.gd(월드를 PNG 로 굽는다)를 만들었다.
바꾼 결정월드 난수는 RandomNumberGenerator 를 안 쓴다. 그건 「몇 번째로 뽑았나」에 값이 달려 있어서, 나중에 청크를 따로 만들거나 타일 하나만 다시 물으면 월드가 달라진다. 좌표를 넣으면 값이 나오는 해시로 갔다 — 순서가 없으니 부분 생성도 같은 답을 준다 (test_tile_query_matches_the_whole_map 이 이걸 지킨다). 그리고 월드를 화면에 그리는 일은 카메라 뒤로 미뤘다 — 섬이 (128,128) 에 있는데 시점은 원점이라, 지금 그리면 화면이 통째로 바다다. 백로그에 한 줄로 넘겼다.
남긴 것대조군 1종이 여전히 헐겁다 — 「검사를 지우면 바닥이 잡는다」가 또 놓쳤다.
잰 값TESTS 35 passed, 0 failed (26 → 35) · PARSE 19개 실패 0 · IMPORT ok · WORLD 256x256 · 씨앗 1 땅 38.07% 0x22a64c62 / 42 땅 39.01% 0x4d412265 / -20260914 땅 28.31% 0xb5650262 · 한 장 190 ms(3장 569 ms · 헤드리스) · 씨앗 12개 땅 비율 28.00% ~ 40.97% · MOVE 가로 239.71 / 대각 238.72 px/s · VIEW 보이는 칸 20.00 x 11.25 · FACE 구간 7 ok · ALL GREEN(기준 5개) · REDTEAM 18 잡음, 1 놓침
채점 1 IMPORT ok · 2 PARSE 19개 스크립트, 실패 0 · 3 TESTS 35 passed, 0 failed · 4 TESTS 35 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 69/90줄$11.4401 누적42e15ec 검사가 35개인데 mintests 바닥은 26 이라 하나쯤 지워도 초록이다. 고치는 자리는 무장된 criteria.tsv 라 세션은 안 건드렸다(bump_mintests 가 올린다). 바퀴 3·5 와 같은 줄이다. 사람이 정할 것: 잡음 상수 넷(BASE_CELLS·CONTRAST·OCTAVES·FALLOFF_POW)은 재서 얻은 값이 아니라 PNG 를 보고 고른 값이다. 땅 38% 는 「섬처럼 생겼나」를 말해 주지 않는다 — 같은 38% 가 도넛일 수도 점 100개일 수도 있다. tools/qa/world_png.gd 로 몇 장 구워 보고 「내 섬」의 느낌이 맞는지 봐야 한다. 씨앗 7 에는 작은 딸린 섬이 하나 붙는데, 그게 좋은 건지 GDD 의 「섬 하나」에 어긋나는 건지도 사람이 정할 일이다. ---
바퀴 5

마우스가 방향을 정한다 — 이동 방향과 별개. 4방향 스냅 + 히스테리시스

2026-09-14 초록
문제실측 게이트에 가짜 커서를 넣을 방법이 없었다. 헤드리스에서는 창이 없고, Input.parse_input_event(InputEventMouseMotion) 을 던져도 뷰포트의 커서가 안 움직여서 get_global_mouse_position() 이 계속 (0,0) 을 준다 — 방향을 재는 게이트가 통째로 무의미해진다.
원인InputEventMouseMotion액션·입력 핸들러로만 흐르고 커서 위치를 안 바꾼다. 위치를 쥔 것은 DisplayServer 라 Input.warp_mouse() 만 그걸 옮긴다. 그리고 warp 는 창 좌표를 받는다 — 월드 좌표를 그대로 넣으면 배율 2.00x 만큼 어긋난 곳을 겨눈다.
고친 것scripts/player_facing.gd 를 새로 만들었다(순수 계산 · class_name PlayerFacing · 스냅 반각 45° · 히스테리시스 10° · 데드존 8px). scripts/player.gd 가 매 물리 프레임 aim_at(get_global_mouse_position()) 을 부르고, scenes/player.tscn 의 코 네모(16x16)를 중심에서 28px 떨어진 자리로 옮긴다. 단위 검사 8개(tools/tests/test_player_facing.gd) · 실측 게이트 tools/tests/measure_facing.gd--headless 없이 창을 띄우고 Input.warp_mouse(월드 * 최종배율)진짜 커서를 옮겨 잰다. tools/loop/check.shVIEW 뒤에 부른다. 알게 된 것 둘은 GOTCHAS.md 에 넣었다.
바꾼 결정몸의 방향과 얼굴의 방향을 분리했다. 이동은 8방향(정규화)인데 바라보는 방향은 4방향이다 — 스프라이트가 앞/뒤/옆뿐이라 대각선을 그릴 그림이 없다. 그래서 PlayerMotionPlayerFacing서로 안 보는 두 클래스로 뒀다 (GDD D-2c). - 잰 값 (창 1920x1080 · 배율 2.00x · 겨눔 거리 200px · 물리 60Hz): FACE 오른쪽 0.0° → (1,0) 코 (28,0) · 아래 90.0° → (0,1) 코 (0,28) · 왼쪽 -179.7° → (-1,0) · 위 -90.0° → (0,-1) · 46° → (1,0) 붙잡음 · 60° → (0,1) 놓음 (구간 7 전부 ok). TESTS 26 passed, 0 failed(18 → 26) · PARSE 15개 실패 0 · IMPORT ok · MOVE 가로 239.63 / 대각 238.78 px/s · VIEW 보이는 칸 20.00 x 11.25 · ALL GREEN(기준 5개). 화면: SHOT 960x540 · 색 4개 · 가장 넓은 한 색 50.0% — 코 네모가 몸 오른쪽에 붙어 보인다.
남긴 것REDTEAM 15 잡음, 1 놓침. 새 대조군 3종(커서를 안 읽음 / 히스테리시스를
채점 1 IMPORT ok · 2 PARSE 15개 스크립트, 실패 0 · 3 TESTS 26 passed, 0 failed · 4 TESTS 26 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 89/90줄$8.1299 누적7128769 건너뜀 / 코가 안 돎)은 셋 다 단위 검사 26개를 초록으로 남긴 채 실측만 잡았다. 놓친 하나는 「검사를 지우면 테스트 바닥이 잡는다」 — 검사가 18 → 26 이 됐는데 계약의 바닥은 아직 mintests.sh 18 이라 하나 지워도 바닥 위다. 바퀴 3 과 똑같은 모양이고 고치는 자리는 무장된 .loop/criteria.tsv 라 세션은 손대지 않았다. 드라이버의 bump_mintests 가 초록 뒤에 18 → 26 으로 올리고 다시 무장한다. ② 히스테리시스 10° 는 실측이 아니라 추정이다. 60Hz 깜빡임을 막는 데는 충분한데 손맛으로 편한 값인지는 사람이 직접 걸어 보고 정해야 한다 (godot.sh 5 -- --path .). ③ 기준 4 가 이제 GUI 세션에 더해 커서까지 뺏는다 — 사람이 쓰는 중이면 1초쯤 튄다. 화면 없는 기계에서는 여전히 빨개진다 (바퀴 4 가 남긴 위험 그대로). ---
사람

바퀴 4 가 드러낸 것 — 대시보드가 굳고, 푸시가 없고, 일지 형식이 깨졌다

2026-09-14 초록
문제바퀴 4 는 초록으로 끝났는데 라이브 페이지는 09:38 것 그대로였다. ① 드라이버가 커밋만 하고 푸시를 안 했다 — 대시보드가 GitHub Pages 인데. ② 로컬에서 구운 페이지는 「루프 돌고 있다」로 굳어 있었다. ③ 세션이 절 끝에 --- 를 덧붙여서 드라이버가 찍는 줄이 절 밖으로 떨어졌고, 찍힌 커밋도 항목을 만든 eebde9b 가 아니라 그 뒤 부기 커밋 83ad8df 이었다.
원인②는 report.py.loop/loop.pid 가 살아 있나 봤는데, 드라이버가 report.py 를 부르는 시점엔 드라이버 자신이 살아 있다. 늘 참이고, 바로 뒤에 루프가 끝나도 페이지는 그대로다. 「지금 돌고 있나」는 구운 스냅샷이 알 수 없는 것인데 알 수 있는 척했다. ③은 stamp 가 절 에 이어 붙였기 때문이고, 커밋 쪽은 finish_journalroll_state 뒤에 돌아서 그 사이 부기 커밋이 HEAD 를 차지했다.
고친 것타일을 마지막 바퀴 (번호 · 결과 · 날짜) 로 바꿨다 — 잰 것만 적는다. push_state() 신설 — 계약 · 항목 verify · 일지를 다 통과한 뒤에만 부른다. stamp마지막 - 무엇: 줄 바로 뒤에 끼우고, 커밋은 head_after 를 받는다. 그리고 report.py 가 실패하면 루프가 멈춘다 — 바깥에서 볼 수 있는 유일한 창이라 조용히 실패하면 페이지가 낡은 채로 며칠을 간다. 실제로 푸터 문구를 고치다 문법이 깨져 깨진 report.py 가 그대로 푸시됐고, 아무것도 빨개지지 않았다.
바꾼 결정초록으로 닫힌 바퀴만 바깥으로 나간다 (사람). 빨간 상태는 푸시하지 않는다. 끄려면 PUSH=0. 푸시 실패는 루프를 죽이지 않는다 — 네트워크는 이 루프의 판정 대상이 아니다.
남긴 것정적 페이지가 「지금」을 말하려 들면 반드시 굳는다. 다음에 상태 타일을 늘릴 때 같은 함정을 조심한다. 그리고 report.py 는 이제 게이트지만 대조군이 없다 — 일부러 깨뜨려서 루프가 진짜 멈추는지 redteam.sh 에 넣을 자리가 남았다.
잰 값REDTEAM 13 잡음, 0 놓침 · ALL GREEN (기준 5) · 라이브 페이지 http 200 · Pages 반영에 20~40초
채점 1 IMPORT ok · 2 PARSE 12개 스크립트, 실패 0 · 3 TESTS 18 passed, 0 failed · 4 TESTS 18 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 79/90줄$5.1658 누적 (사람 세션 — 루프 예산 밖)0bf4a5b
바퀴 4

논리 960×540 · 정수 2배 · 보이는 칸 20 × 11.25

2026-09-14 초록
문제항목이 이미 끝나 있었다. test_project_settings.gd 가 960×540·1920×1080· integer·20×11.25 를 전부 강제하고 있어서 손댈 것이 없었다 — 그런데 그게 재는 것은 project.godot 의 글자뿐이다. 바퀴 3 에서 이미 같은 모양의 구멍을 봤다 (단위 검사 18개가 전부 초록인데 노드가 속도를 반으로 줄여도 안 잡혔다).
원인설정 값을 읽는 검사는 실행 중에 값이 덮이는 것을 못 본다. 카메라 줌 한 줄, window_set_size 한 줄이면 눈에 보이는 칸이 달라지는데 ProjectSettings.get_setting() 은 여전히 960 을 준다.
고친 것실측 게이트 tools/tests/measure_view.gd 를 만들었다 — 실제 창을 띄워 메인 씬을 5프레임 돌리고 논리 화면·창·배율·카메라 배율·보이는 칸을 잰다. 배율은 나눗셈이 아니라 root.get_final_transform(), 보이는 칸은 get_canvas_transform() 으로 월드 좌표로 환산해서 잰다. tools/loop/check.shtests 단계가 MOVE 뒤에 --headless 없이 부른다. 대조군 2종을 tools/loop/redteam.sh 에 박았고 docs/NUMBERS.md 1절· docs/GOTCHAS.md 에 잰 값과 함정을 적었다.
바꾼 결정없음. 다만 카메라 줌 = 1 을 지금부터 게이트가 강제한다 — BACKLOG P1 의 「줌 없음」이 나중 항목이 아니라 이미 잠겼다.
남긴 것헤드리스로는 배율을 못 잰다(창 크기가 (0,0)). 그래서 계약의 기준 4 가 GUI 세션을 필요로 한다 — 화면 없는 기계에서 루프를 돌리려면 이 게이트를 분리해야 한다. 사람이 볼 것: 20×11.25 칸이 실제로 답답하지 않은지는 숫자가 아니라 눈이 정한다 (바퀴 3 의 screencapture 권한 문제가 그대로 남아 있다).
잰 값VIEW 논리 960x540 · 창 1920x1080 · 배율 2.00x · 카메라 1.00x · 타일 48px · 보이는 칸 20.00 x 11.25 (드라이버 macOS · 5프레임 뒤) · TESTS 18 passed, 0 failed · MOVE 가로 238.41 px/s · 대각 238.35 px/s (실이동 240.00 px) · PARSE 12개 실패 0 · ALL GREEN (기준 5개). 대조군: 카메라 zoom=(2,2) → 보이는 칸 10.00 x 5.62 · 실행 중 창 1600×900 → 배율 1.00x. 둘 다 단위 검사 18개는 전부 초록으로 남았다 — 새 게이트만 잡았다. 전체 대조군: REDTEAM 13 잡음, 0 놓침. 바퀴 3 이 놓친 「검사를 지우면 테스트 바닥이 잡는다」도 이제 잡는다 — 사람이 mintests 바닥을 8 → 18 로 올려 놨기 때문이다.
채점 1 IMPORT ok · 2 PARSE 12개 스크립트, 실패 0 · 3 TESTS 18 passed, 0 failed · 4 TESTS 18 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 79/90줄$5.1658 누적eebde9b
사람

문서를 docs/ 로 · 바퀴 일지 · GitHub Pages 대시보드

2026-09-14 초록
문제바깥에서 루프가 어떤 상태인지 볼 방법이 없었다. .loop/results.json · spend.txt · STOPPED 은 전부 .gitignore 라 GitHub 에서 안 보이고, md 가 루트에 흩어져 있어 나중에 다시 볼 때 어느 파일이 어느 층인지 헷갈렸다. 그리고 일지가 없었다.loop/state.md 는 만든 것 위주라 「무엇이 막혔고 왜 그랬나」가 바퀴마다 새면서 사라졌다.
원인기록의 독자를 나눈 적이 없다. state.md 는 다음 바퀴의 세션이 읽는 것이고, 사람이 몇 주 뒤에 읽을 기록은 따로 필요한데 같은 파일에 섞여 있었다. 그런데 state.md 는 롤링되므로 사람이 읽을 것까지 같이 잘려 나갔다.
고친 것md 를 전부 docs/ 로 옮겼다 (CLAUDE.md 만 루트 — Claude Code 가 루트만 자동 로드한다). docs/JOURNAL.md 신설 — 세션은 읽지 않고 끝에 절만 덧붙인다. tools/loop/journal.shnext/check/stamp 를 맡고, tools/loop/report.pydocs/index.html 을 굽는다. 드라이버가 매 바퀴 끝에 찍고 굽고 커밋한다.
바꾼 결정일지의 줄마다 주인을 갈랐다. 세션이 문제·원인·고친 것·바꾼 결정·잰 값·남긴 것 을, 드라이버가 날짜·결과·채점·비용·커밋 을 쓴다. 채점 칸에는 results.json — 채점자가 쓴 것 — 만 들어가므로 세션의 「됐습니다」가 결과가 되지 않는다. 그리고 빨간 바퀴에도 일지는 남긴다git reset --hard 전에 빼뒀다가 도로 넣는다. 왜 빨갰는지가 제일 비싼 기록인데 지금까지는 되돌리기에 같이 쓸려 나갔다. 세션이 사람 판단 줄을 비워 두면 초록이어도 멈춘다 (journal.sh check).
남긴 것dry-run 이 버그 둘을 잡았다cat PROMPT.md 가 이동을 못 따라가 지시서가 통째로 빠졌고(81줄), dry-run 은 finish_journal 을 건너뛰어 일지 게이트가 항상 빨갰다. 둘 다 고쳤다. 경로를 옮길 때 --dry-run 을 먼저 돌린다 를 규칙으로 삼을 만하다. 대시보드는 값을 페이지에 구워 넣는다 — 굽지 않으면 낡는다. 사람이 손으로 볼 땐 tools/loop/ctl.sh report.
잰 값REDTEAM 11 잡음, 0 놓침 · ALL GREEN (기준 5) · TESTS 18 passed, 0 failed · MOVE 가로 238.55 px/s · 대각 239.93 px/s (둘 다 240.00 px) · DOCLEN CLAUDE.md 41/45 · docs/PROMPT.md 60/70 · .loop/state.md 59/90 · 조립된 프롬프트 122 → 141줄 (일지 지시 19줄. 바퀴마다 늘지 않는 고정분이다)
채점 1 IMPORT ok · 2 PARSE 11개 스크립트, 실패 0 · 3 TESTS 18 passed, 0 failed · 4 TESTS 18 passed, 0 failed · 5 DOCLEN CLAUDE.md 41/45줄 / DOCLEN docs/PROMPT.md 60/70줄 / DOCLEN .loop/state.md 59/90줄$2.7869 누적 (사람 세션 — 루프 예산 밖)7533cb0
사람

문서 구조 세분화 (매 바퀴 읽는 비용을 고정으로)

2026-09-13 초록
문제매 바퀴 읽는 문서가 바퀴마다 비싸졌다. .loop/state.md 가 3바퀴에 59줄인데 이 기울기면 34바퀴 뒤 ~600줄 ≈ 24,000 토큰이 된다. 1판을 죽인 것이 이 기울기였다. 그리고 중복이 하나 — 드라이버가 prompt.txtPROMPT.md 를 통째로 넣는데 그 PROMPT.md 가 다시 "PROMPT.md 를 봐라" 라고 시켜서 두 번 들어갔다.
원인문서를 「층」으로 나누지 않고 전부 매 바퀴 읽게 뒀다. 고정 비용이 아니라 기울기가 문제인데, 절대량만 보고 있어서 안 보였다.
고친 것문서를 고정 / 주입 / 조건부 / 롤링 네 층으로 갈랐다. GOTCHAS.md 를 신설해 함정을 NUMBERS.md 에서 떼냈고(에러 났을 때만 grep), PROMPT.md 맨 위에 "드라이버가 넣어준다, 다시 열지 마라" 를 박아 중복을 없앴다. 얇음을 게이트로 만들었다tools/loop/doclen.sh 를 계약 기준 5로 올려서 세션이 문서를 부풀리면 그 자리에서 빨개진다.
바꾼 결정.loop/state.md 는 세션이 열지 않는다. 드라이버가 최근 2바퀴만 조립해 넣고, 3바퀴가 넘으면 .loop/archive/ 로 뺀다.
남긴 것없음
잰 값고정 문서 5,019 → 3,281 토큰 · 34바퀴 뒤 state.md ~24,000 → ~2,000 토큰으로 고정 · REDTEAM 11 잡음, 0 놓침 · 게이트가 만들자마자 PROMPT.md 72줄 > 상한 70 을 잡아서 57줄로 줄였다
71f9b89
사람

8방향 확정 · 화면 QA 도구

2026-09-13 초록
문제바퀴 3 이 「4방향 이동」 항목을 하면서 대각선을 정규화해 사실상 8방향을 만들었고, 이건 사람이 판정할 것이라 세션이 스스로 확정할 수 없었다. 그리고 화면을 눈으로 못 봤다 — screencapture 가 macOS 화면 기록 권한에 막힌다(could not create image from display).
원인WASD 두 개를 동시에 누를 수 있는 이상 대각선은 생긴다. 막느냐 정규화하느냐는 게임 감각의 문제라 검사로 정할 수 없다. 캡처 쪽은 무인 루프가 권한 대화상자를 넘길 방법이 없다.
고친 것tools/qa/shot.gd + tools/loop/shot.shGodot 자신의 프레임버퍼를 PNG 로 굽는다. 권한이 필요 없고, 화면에 보이는 것이 아니라 게임이 그린 것을 읽으므로 창이 겹쳐도 상관없다.
바꾼 결정8방향으로 확정 (사람). 코어 키퍼(GDD A-4)도 8방향이고, 속력은 어느 방향이든 240 이다.
남긴 것「저장됨」은 증거가 아니라서 크기·색 수·가장 넓은 한 색 비율을 같이 찍게 했다. 그림이 맞는지는 여전히 사람 몫이다.
잰 값SHOT 960x540 색 3개 가장 넓은 한 색 50.0% · 헤드리스로는 텍스처가 비어 출력 없음(3b절) · 대조군 상한 40% → 빈 씬(50%) 잡힘 / 없는 씬 → SHOT ERROR
a56666b
바퀴 3

P1-1 색 네모 플레이어 · 48px 타일 · 240 px/s · WASD

2026-09-13 초록
문제단위 검사 18개가 전부 초록인데 게임이 틀린 경우가 실제로 나왔다. 대조군으로 노드가 속도를 절반으로 만들게 했더니 검사는 하나도 안 빨개졌다. 그리고 대조군 자동화가 REDTEAM 9 잡음, 1 놓침 을 냈다 — 「검사를 지우면 테스트 바닥이 잡는다」가 놓쳤다.
원인순수 계산(PlayerMotion)만 검사하면 노드가 그 값을 실제로 쓰는지는 아무도 안 본다. 놓침 쪽은 검사가 8 → 18 로 늘었는데 계약의 바닥은 아직 mintests.sh 8 이라, 하나 지워 17 이 돼도 바닥 위였다. 가짜 게이트가 된 건 아니고 헐거워졌다.
고친 것실측 게이트 tools/tests/measure_move.gd 를 만들어 check.sh tests 가 단위 검사 뒤에 부르게 했다 — 실제로 1초 걸어서 이동 거리를 잰다. 바닥 문제는 드라이버의 bump_mintests (loop.sh) 가 초록 뒤에 8 → 18 로 올리고 다시 무장한다. 세션은 .loop/criteria.tsv 에 손대지 않았다 (red line).
바꾼 결정대각선 정규화로 사실상 8방향이 됐다 — 사람이 확정할 때까지 되돌릴 수 있게 적어 뒀다.
남긴 것화면을 눈으로 못 봤다screencapture 권한 문제. 다음 사람 세션에서 해결. P1-2 는 test_project_settings.gd 가 이미 강제 중이라 거의 끝나 있다 — 확인부터 한다.
잰 값TESTS 18 passed, 0 failed (바닥 8) · PARSE 9개 실패 0 · IMPORT ok · ALL GREEN · 실측(헤드리스 · 물리 60Hz · 1초) 가로 238.97 px/s · 4.979칸/s 대각 239.91 px/s · 4.998칸/s (둘 다 실제 이동 240.00 px, 기대 240 ±5) · 대조군 정규화 제거 → 339.41 잡음 · 속도 절반 → 단위 18개 전부 초록, 실측만 잡음(119.36)
440359e
바퀴 2

P0-3 계약 배선 (증거를 기계가 쓴다)

2026-09-13 초록
문제results.json 의 증거가 한 줄로 뭉쳐 있었다. 그리고 「공허한 계약」 게이트가 진짜로 도는 코드인지 확인할 수 없었다 — 무장 중에는 변조(77)가 먼저 걸려서 78 을 못 본다.
원인BSD tr\x1f 를 못 읽는다. 구분자가 문자 x 가 되어 증거 줄이 안 갈렸다. macOS 의 tr 은 8진수만 받는다.
고친 것구분자를 \037 로 바꿨다. 공허 게이트는 무장을 풀고 다시 재서 죽은 코드가 아님을 확인했다. 그리고 contract.md 를 따로 두지 않고 정지 규칙·red lines 를 PROMPT.md 에 합쳤다 — 매 바퀴 읽는 문서 수를 하나라도 줄이는 쪽이 낫다.
바꾼 결정판정은 세션이 아니라 run-contract.sh 가 한다. 이것만이 results.json 을 쓴다.
남긴 것P0-4 대조군을 스크립트로 (redteam.sh) → 그다음 드라이버
잰 값ALL GREEN (기준 3개) · 대조군 5종 정상 0 / 기준 실패 1 / 변조 77 / 공허 78 / 파일 없음 78
6e135c8
바퀴 1

P0-1 · P0-2 Godot 뼈대 + 헤드리스 러너 + 프로젝트 로컬 격리

2026-09-13 초록
문제격리했다고 믿었는데 안 돼 있었다. XDG_DATA_HOME 등을 설정해두고 넘어갈 뻔했다.
원인**macOS Godot 은 XDG_* 를 무시한다. ~/Library/Application Support/Godot 에 그대로 쓴다. 재보니 홈의 Godot 디렉터리 mtime 이 실행 시각으로 갱신되고 있었다. 설정해두고 「됐다」고 믿는 것이 정확히 가짜 게이트다.**
고친 것tools/loop/godot.shHOME 자체를 프로젝트 안으로 돌린다. 그리고 격리 자체를 검사로 박았다 (tools/tests/test_isolation.gd) — godot.sh 를 우회한 실행은 그 자리에서 빨개진다.
바꾼 결정Godot 의 입구는 godot.sh 하나다. 다른 데서 직접 부르지 않는다. 그리고 check.shimport → parse → tests 순서다 — class_name 전역 클래스가 임포트가 만드는 캐시에 들어가서, 순서를 바꾸면 멀쩡한 코드가 빨개진다.
남긴 것두 항목을 한 바퀴에 넣었다 — P0-1 의 verify 가 check.sh all 이라 P0-2 없이는 검증 자체가 성립하지 않는다. 「한 바퀴 한 항목」에서 벗어난 것이라 적어 둔다.
잰 값IMPORT ok · PARSE 5개 실패 0 · TESTS 8 passed, 0 failed · 실제 창 VIEWPORT 960x540 · WINDOW 1920x1080 · SCALE 2.00x · 대조군 5종 전부 빨개짐 → 원복 후 초록
908f5aa

문서 — 누가 언제 읽나

CLAUDE.md 고정 매 바퀴 · 자동 로드 이 기계에서 무엇을 어떤 명령으로 돌리는가만. 부풀면 문맥이 오염된다 PROMPT.md 주입 매 바퀴 · 드라이버가 넣는다 한 바퀴 4단계 · 정지 규칙 6개 · red lines. 세션은 이 파일을 열지 않는다 BACKLOG.md 주입 매 바퀴 · 한 줄만 할 일. 한 줄 = 한 바퀴. 순서는 종속성이지 중요도가 아니다 NUMBERS.md 조건부 값을 쓸 때 · 그 절만 실측값. 잰 조건을 같이 적는다 — 「passed」는 증거가 아니다 GOTCHAS.md 조건부 에러가 났을 때만 · grep 엔진·셸의 함정. 조건부라서 의도적으로 계속 쌓는다 GDD.md 조건부 그 영역을 처음 만들 때만 게임 기획서 — 「왜」. 매 바퀴 읽지 않는다. 세션이 못 고친다 JOURNAL.md 사람 세션은 안 읽는다 바퀴 일지. 무엇이 막았고 왜 그랬고 그래서 무엇을 바꿨나

최근 커밋