뜯어본 생각공개

오토플레이 테스트 툴

봇이 맵을 돌며 성능을 재는 툴. 끼임을 「데이터의 모순」으로 잡는 방식이 제 것보다 나았습니다 — 그래서 모순 감지와 두 시계 확정을 둘 다 제 봇에 넣었습니다(2026-09-28).

게임 안에 「혼자 돌아다니는 봇」을 넣어서, 사람 없이 성능을 재게 만든 도구입니다. 저도 같은 일을 하고 있는데, 이 사람들이 한 겹을 더 쌓아 뒀습니다.

제약 둘이 설계를 통째로 결정했다 벽 1 — 조합 플랫폼별 그래픽 옵션을 합치면 약 30가지 매 빌드마다 손으로? 불가능하다 벽 2 — 플랫폼 콘솔은 외부 자동화 도구를 못 붙인다 → 게임 패키지 안에 탑재하는 수밖에 그래서 철학이 하나로 정해진다 완벽한 재현은 포기한다 — 무슨 일이 있어도 멈추지 않고 데이터를 이어 모은다 ⇒ 뒤에 나오는 설계 넷이 전부 이 한 문장에서 파생된다 폴백도 타임아웃 가드도 텔레포트도, "멈추지 않는다"를 지키려고 붙은 장치다
제약이 둘뿐인데 설계 전체가 거기서 나온다. 철학이 먼저가 아니라 제약이 먼저였다.

왜 봇을 만들어야 했나 — 사람이 30번 똑같이 걸을 수 없어서

게임이 버벅이는지 확인하려면 같은 구간을 같은 속도로 걸으면서 초당 몇 장이 그려지는지 재야 합니다.

문제는 그래픽 설정이 30가지라는 것입니다. 하나 고칠 때마다 30번을 재야 하고, 그것도 매번 똑같이 걸어야 합니다. 사람이 하면 걸음이 매번 달라져서 비교 자체가 안 됩니다.

비유하면 체중계 위에 매번 다른 자세로 올라가는 것과 같습니다. 숫자는 나오는데 비교할 수가 없습니다.

그래서 봇을 만들었습니다. 봇은 매번 정확히 같은 길을 같은 속도로 걷습니다.

저도 같은 걸 하고 있었습니다 — 그런데 판정 방식이 달랐습니다

제 일도 봇을 맵에 풀어 놓고 어디서 끼어서 못 움직이는지 찾는 것입니다. 그런데 「끼었다」를 판정하는 방식이 달랐습니다.

제 방식: 「N초 동안 위치가 안 변하면 끼임」

간단하고 대체로 맞습니다. 그런데 큰 구멍이 있습니다. 일부러 멈춰 선 것도 끼임으로 셉니다. 봇이 대화를 기다리거나 문이 열리길 기다리는 정상 상태가 전부 오탐이 됩니다.

이 사람들 방식 — 「말과 행동이 안 맞는 것」을 본다

여기가 배운 것입니다. 위치만 보지 않고 둘을 같이 봅니다.

보는 것 뜻
가려고 힘을 주고 있나 봇이 앞으로 가려는 입력을 넣고 있는가
실제로 움직이고 있나 위치가 실제로 변하고 있는가

그리고 이 둘이 어긋날 때만 끼임 후보로 봅니다.

  • 힘도 안 주고 안 움직임 → 정상 (기다리는 중)
  • 힘은 주는데 안 움직임 → 끼임 후보

차가 액셀을 밟는데 안 나가는 것과, 주차해 둔 것의 차이입니다. 위치만 보면 둘이 똑같이 보입니다.

그 위에 한 겹을 더 쌓았습니다 — 「두 시계가 동시에 울려야 확정」

후보를 잡았다고 바로 끼임으로 치지 않습니다. 시계 두 개를 따로 돌립니다.

시계 재는 것 얼마
첫째 힘은 주는데 안 움직인 시간 1.5초
둘째 목적지까지 거리가 안 줄어든 시간 1.2초

그리고 둘 다 울려야 끼임으로 확정합니다. 하나만 울리면 넘어갑니다.

왜 이렇게 하냐면, 각각은 정상인데도 울릴 수 있기 때문입니다. 제자리에서 도는 중이면 첫째가 울리고, 우회해서 돌아가는 중이면 둘째가 울립니다. 둘 다 울리는 상황은 정상적으로는 잘 안 나옵니다.

→ 정리하면 한 층은 후보를 만들고, 다른 층은 잘못 잡힌 것을 걷어냅니다. 저는 이 둘을 한 층으로 뭉쳐 놨던 것입니다.

그리고 길을 못 찾을 때 세 번까지 다르게 시도합니다

가져오진 않았지만 잘 만든 부분이라 적어 둡니다.

  1. 평소대로 길을 찾아본다
  2. 안 되면 자주 끼는 구간을 「가지 말 것」으로 막고 다시 찾는다
  3. 그래도 안 되면 높낮이 변화를 전부 무시하고 평지 위주로 찾는다
  4. 셋 다 실패하면 — 그냥 순간이동시킨다

마지막이 좋습니다. 완벽하게 만들려고 붙들지 않고 「못 하면 건너뛴다」를 미리 정해 둔 것입니다. 이 도구의 목적은 길찾기가 아니라 성능 측정이라서, 길찾기에서 막혀 밤새 서 있는 게 제일 나쁜 결과입니다.

인용하면 안 되는 숫자 둘

「1.5초·1.2초」 — 그대로 안 씁니다. 왜 그 숫자인지가 발표에 없습니다. 제 환경은 봇 속도도 맵 크기도 다르니 제가 다시 재야 합니다. 남이 잰 값을 근거 없이 옮기면, 틀렸을 때 왜 틀렸는지도 모릅니다.

「성능 48.9% 개선」 — 이건 인용 금지입니다. 이 숫자는 최적화 작업이 만든 것이지 이 도구가 만든 게 아닙니다. 도구는 그걸 잴 수 있게 해 줬을 뿐입니다. 자를 잘 만들었다고 자가 살을 빼 준 게 아닙니다.

이 편에서 값을 치른 교훈

같은 문제를 이미 풀고 있어도, 남이 한 겹 더 쌓아 둔 것은 보입니다.

제가 시간으로 끼임을 판정한 게 틀린 건 아니었습니다. 다만 후보를 잡는 층과 오탐을 걷는 층을 나누지 않았을 뿐입니다. 층을 나누고 나니 오탐의 원인이 이름을 갖게 됐습니다 — 「일부러 멈춰 선 정상 상태」.

이름이 붙으면 고칠 수 있습니다. 이름이 없는 동안에는 「가끔 이상하게 잡힌다」로만 남습니다.

채택한 둘은 점검 때 코드에 없어서 그날 넣었고, 제 쪽 설명 하나는 틀렸습니다 (2026-09 점검)

한 달쯤 지나 제 코드를 다시 열어 봤습니다.

틀린 설명. 위에서 「후보를 잡는 층과 오탐을 걷는 층을 나누지 않았다」고 적었는데, 제 끼임 판정에도 둘째 층이 이미 있었습니다(7월 코드). 3초 동안 60cm를 못 가면 후보로 잡고, 봇을 옆·뒤로 네 번 빠져나가 보게 해서 2m 넘게 벗어나면 «피할 수 있었음»으로 걷어냅니다. 층을 나누는 생각 자체는 있었던 것입니다. 이 편을 쓸 때 제 코드를 다시 안 열어 본 탓입니다.

정말 없던 것. 「가려고 힘을 주는데 안 움직인다」는 모순 감지는 여전히 없었습니다. 위치 기록 형식에 «움직이려는 중» 칸이 적혀만 있고, 그 칸을 채우는 곳도 판정에 쓰는 곳도 없었습니다. 두 시계를 함께 보는 확정도 없었습니다.

→ 이 편에서 「채택」이라고 적은 둘은 점검 때 코드에 없었습니다. 그리고 제 것을 설명하면서 제 코드를 안 열어 본 실수는 이 칸에서 두 번째입니다 — 에이슬립 편에서도 한 번 고쳤습니다.

(2026-09-28) 모순 감지는 넣었습니다. 봇이 위치를 잴 때마다 「직전에 이동을 밀고 있었나」를 같이 적고, 3초 내내 밀었는데도 60cm를 못 갔을 때만 끼임 후보로 봅니다. 한 순간이라도 안 밀었으면(착지·대기) 후보가 아닙니다. 스위치를 끄면 기다리는 봇도 끼임으로 잡히는 것까지 테스트로 확인했습니다.

다만 지금 봇은 걷는 동안 매 순간 밀고 있어서, 시뮬레이션 60칸에서는 켜나 끄나 끼임 후보가 두 번으로 같았습니다. 이 장치는 봇에 «기다리는» 동작이 생길 때 값을 합니다.

(2026-09-28) 두 시계 확정도 넣었습니다. 이제 끼임으로 치려면 시계 두 개가 같이 울려야 합니다.

시계 무엇이 울리게 하나
첫째 (있던 것) 3초 내내 밀었는데 60cm 를 못 갔다
둘째 (새로 넣은 것) 목적지까지 거리가 3초 동안 10cm 도 안 줄었다

그래서 거의 제자리여도 목적지 쪽으로 조금씩 나아가고 있으면 끼임이 아닙니다. 진흙에 빠진 차가 바퀴를 굴리며 한 뼘씩이라도 나가고 있다면, 아직 「갇혔다」고 부를 때가 아닌 것과 같습니다.

넣고 나서야 안 것이 하나 있습니다. 저는 이 자리를 탈출 시도가 이미 맡고 있다고 봤는데, 조금씩 기어가는 봇은 탈출 시도로 못 걸렀습니다. 옆으로 빠져나가 보라고 해도 옆으로도 기어가니 2m 를 못 벗어나고, 결국 끼임으로 기록됐습니다(테스트로 확인). 이제는 그런 곳이 끼임 대신, 20초 안에 목적지에 못 닿으면 「못 가는 곳」으로 남습니다.

3초·10cm 는 제가 고른 값입니다 — 발표의 1.5초·1.2초를 안 가져온 것과 같은 이유로, 실제 게임에서 다시 재야 합니다. 시뮬레이션 60칸에서는 이번에도 켜나 끄나 끼임 후보가 두 번으로 같았습니다(시뮬레이션에는 기어가는 경우가 없습니다). 실제 게임에서는 아직 안 돌렸습니다.

여기부터는 자세한 기록입니다

제가 이미 하고 있는 일을 더 잘하는 방법이 여기 있었습니다. 콘솔 게임에 탑재형 무인 오토플레이 성능 테스트 툴을 만든 발표입니다. 그래픽 옵션 30가지를 매 빌드마다 손으로 재는 게 불가능해서 봇이 스스로 맵을 돌며 성능을 측정하게 만들었습니다. 저도 봇을 맵에 풀어 놓는 일을 하고 있는데, 끼임을 판정하는 방식이 제 것보다 정확했습니다 — 그리고 그 차이가 코드 몇 줄입니다.

무엇을 만든 물건인가

게임 패키지 안에 들어가는 무인 테스트 봇입니다. 성능 최적화는 “측정 → 작업 → 같은 조건에서 재측정” 의 반복인데, 같은 조건을 30번 만들어 주는 사람이 없으면 성립하지 않습니다. 그래서 봇이 대신 돕니다.

콘솔이 설계를 지배했습니다. PC라면 밖에서 스크립트를 붙이면 되는데, 콘솔은 보안·규격상 외부 도구를 못 붙입니다. 그러니 자동화가 게임 안에 있어야 하고, 이 전제가 이후 전부를 결정합니다.

네 덩어리로 만들었습니다 — 경로 탐색 · 전투 · 미션 판별 · 아키텍처.

덩어리 무엇을 푸나
경로 탐색 복잡한 레벨에서 봇이 길을 못 찾거나 끼는 문제
전투 몬스터가 수십 마리 쏟아질 때 타겟 스캔이 프레임을 먹는 문제
미션 판별 사람이 입력한 데이터의 오타로 매칭이 깨지는 문제
아키텍처 한 곳이 멈추면 전체가 서는 문제

대표 기법 — 끼임을 「데이터의 모순」으로 잡는다

길찾기는 3단계 폴백입니다. ①표준 경로 탐색 → ②잘 끼는 연결 구간의 비용을 무한대로 올려 우회 → ③연결 구간과 높이 변화를 전부 배제하고 평지 우선. 셋 다 실패하면 텔레포트합니다.

그런데 제일 좋은 건 끼임을 감지하는 방식입니다. 길찾기는 “통과 가능” 이라고 했는데 실제로는 좁은 틈에 낀 상태 — 이걸 두 값의 모순으로 잡습니다.

  • 가속도가 있다 — 이동 입력은 유지되고 있다. 봇은 가려고 한다.
  • 평면 속도가 10cm/s 미만이다 — 실제로는 안 가고 있다.

둘이 동시에 참이면 물리적으로 모순이고, 그게 끼임입니다.

그리고 여기서 한 겹을 더 씁니다. 즉시 텔레포트하면 오탐이 납니다 — 스킬을 쓰느라 또는 상호작용하느라 의도적으로 멈춘 정상 상태를 끼임으로 오인합니다. 그래서 타이머 둘의 AND 게이트로만 확정합니다.

타이머 재는 것 임계
입력은 있는데 안 움직인 누적 시간 가속도 있음 + 속도 <10cm/s 1.5초
목표까지 거리가 안 줄어든 누적 시간 벽에 막혀 제자리걸음 1.2초

둘 다 넘는 순간만 진짜 끼임입니다. 단순한 논리 하나로 길찾기 우회 로직을 통째로 걷어냈습니다.

나머지 둘도 실용적입니다. 전투는 전체 스캔을 캐시가 비었을 때만 하고 이후엔 가까운 K개만 갱신합니다 — 몬스터가 어그로로 다가온다는 성질을 이용한 겁니다. 미션 판별은 문자열 비교 대신 편집 거리로 유사도 75% 이상이면 같은 타입으로 봅니다. 사람이 입력한 데이터의 오타 하나로 봇이 미션에서 이탈하는 것을 막는 장치입니다.

깨본 결과

끼임 판정 — 후보를 모순으로 잡고, 확정을 AND 로 건다 모순 감지 가려는 입력은 있는데 실제 속도는 10cm/s 미만 타이머 둘의 AND 안 움직인 시간 1.5초 초과 거리 안 줄어든 시간 1.2초 초과 확정 → 텔레포트 멈추지 않고 계속 돈다 데이터 수집이 안 끊긴다 내 쪽은 시간 하나로만 판정한다 — 그래서 의도적으로 멈춘 것도 끼임으로 센다 모순 감지가 후보를 좁히고, AND 게이트가 오탐을 걷는다. 둘이 다른 일을 한다 ⚠ 그리고 이 툴이 못 잡는 것을 발표가 먼저 말한다 — 시각 버그는 판단 불가 성능은 숫자로 나오지만 "화면이 이상한가"는 봇이 못 본다. 그 자리는 여전히 사람이다
모순 감지와 AND 게이트는 같은 문제를 푸는 게 아니다. 하나는 후보를 만들고 하나는 오탐을 걷는다.

수치가 붙어 있는 게 이 발표의 강점입니다. 최적화 사례를 렌더 스레드 시간 평균 −48.9%, 상위 5% 구간 −42.6% 로 제시하고, 툴 자체 오버헤드는 프레임당 최대 1.1ms(목표 2ms 이내)라고 밝힙니다. 재는 도구가 재는 대상을 얼마나 방해하는지를 같이 재서 내놓은 것이 드뭅니다.

한계도 스스로 말합니다 — 시각 버그는 판단 불가. 성능은 숫자로 나오지만 “화면이 이상한가” 는 봇이 못 봅니다.

⚠ 다만 −48.9%는 최적화 작업의 효과이지 툴의 효과가 아닙니다. 툴은 그 효과를 재준 것입니다. 인용할 때 이 둘을 붙이면 과장이 됩니다.

⚠ 그리고 임계값 1.5초·1.2초의 근거가 없습니다. 어떻게 정했는지, 다른 값에서 오탐이 어땠는지가 발표에 없습니다. 제 환경에 그대로 가져오면 안 되고 다시 재야 하는 숫자입니다.

내 환경에 대봤다

저도 봇을 맵에 풀어 놓고 걷다 빠지는 지점을 찾습니다. 그런데 끼임 판정을 시간 하나로만 하고 있었습니다 — “N초 동안 위치가 안 변하면 끼임”. 그래서 의도적으로 멈춘 정상 상태를 끼임으로 셉니다.

모순 감지가 제 쪽에 없던 것입니다. “가려는 입력은 있는데 실제로 안 간다” 는 위치만 봐서는 절대 안 나오는 신호입니다. 위치가 안 변한 것은 끼임일 수도 있고 그냥 서 있는 것일 수도 있는데, 입력을 같이 보면 둘이 갈립니다.

AND 게이트는 그다음 층입니다. 모순이 잡혀도 순간적일 수 있으니, 두 시계가 동시에 임계를 넘을 때만 확정합니다. 제 것은 층이 하나뿐이라 후보와 확정이 같은 판정이었습니다.

전투 캐싱과 미션 판별은 제 상황과 안 맞습니다. 제 봇은 전투를 안 하고, 미션 이름을 문자열로 매칭하는 자리도 없습니다.

(2026-09 기준 정정) 위의 「층이 하나뿐」은 틀렸다. 네브메시 봇의 끼임 판정(2026-07-22 코드)은 이미 두 층이다 — detect_stuck 이 마지막 3.0초 창의 수평 이동 폭이 60cm 미만이면 후보를 내고, 그다음 옆·뒤로 탈출을 4회(한 방향 0.8초, 원점에서 300cm 쪽) 시도해 200cm 넘게 벗어나면 회피 가능(결함 아님), 4회 다 실패해야 Stuck 으로 기록한다. 후보와 확정은 이미 갈라져 있었고, 확정을 «둘째 타이머» 대신 «탈출 시도»로 했을 뿐이다. 없는 것은 모순 감지 하나다 — 샘플 형식 docstring 에 moving 이 적혀 있지만 채우는 곳도 읽는 곳도 없다. (2026-09-28 — 이제 채우고 읽는다. 아래 판정 표 뒤.)

판정

무엇 판정
모순 감지 — 입력과 실제를 같이 본다 채택. 위치만 봐서는 안 나오는 신호다
타이머 AND 게이트로 오탐 걷기 채택. 후보와 확정을 층으로 나눈다
3단계 경로 폴백 보류. 제 쪽은 폴백이 필요할 만큼 레벨이 복잡하지 않다
전투 K개 캐싱 · 편집 거리 매칭 해당 없음. 그 문제가 제게 없다
1.5초 · 1.2초 임계값 ⚠ 그대로 쓰지 않는다. 근거가 발표에 없다 — 제 환경에서 다시 잰다
−48.9% 수치 인용 ⛔ 금지. 최적화의 효과이지 툴의 효과가 아니다

(2026-09 기준) 위 표의 «채택» 둘은 아직 코드에 없다. 타이머 AND 게이트가 하려던 일(후보와 확정을 가르기)은 탈출 시도가 이미 하고 있어서, 비어 있는 자리는 모순 감지 — 봇이 «가려는 중»인지를 샘플에 싣고 그 칸이 켜진 창에서만 끼임 후보를 내는 것 — 하나다. 1.5초·1.2초를 가져오지 않는다는 판정은 그대로다.

(2026-09-28) 모순 감지가 들어갔다. 봇의 _push() 가 이동 요청의 반환(밀었으면 True, 이미 그 자리라 안 밀었으면 False)을 pushing 으로 남기고, 그 값이 다음 샘플의 moving 이 된다. 새 목표·텔레포트 진입 때는 False. detect_stuck 은 끼임 창 안 샘플이 전부 moving 일 때만 후보를 낸다(STUCK_NEEDS_PUSH, 끄면 종전). moving 칸이 없는 옛 샘플은 밀던 것으로 본다. 속도(velocity)는 쓰지 않는다 — 입력은 봇 쪽 명령으로 잰다. 테스트 121 → 133 — 스위치를 끈 대조군에서 «안 미는» 봇이 끼임으로 잡히는 것으로 게이트가 무는 것을 확인했다. ⚠ 시뮬 60칸에서 끼임 후보는 켜고 끔 모두 2회 — 지금 봇은 WALK 동안 매 틱 민다. 실게임 주행은 아직이다. 타이머 AND 게이트는 이 시점엔 없었다 — 같은 날 이어서 넣었다(아래).

(2026-09-28) 타이머 AND 게이트도 들어갔다. 둘째 시계는 detect.no_progress_time(samples, target) — 목적지까지 거리가 마지막으로 STUCK_NOPROG_EPS(10cm) 이상 새 최저를 찍은 뒤 흐른 시간이다. 최저를 그만큼 갱신할 때만 0으로 돌아가므로 벽 앞에서 앞뒤로 흔들리는 것은 다가감이 아니다. 창 처음과 최저를 비교하는 방식이었다면 20cm 흔들림 한 번이 «다가감»이 된다(흔들림 샘플이 여전히 후보로 잡히는 것은 테스트로 확인). detect_stuck 은 목적지를 받으면 첫째 시계(위의 끼임 창 + 모순) 뒤에 둘째가 STUCK_NOPROG_TIME(3초)을 넘겼는지까지 본다(STUCK_NEEDS_NOPROG, 끄면 종전). 목적지를 안 주는 호출도 종전대로다.

⚠ 10cm·3초는 고른 값이다 — 1.5초·1.2초를 안 가져온 판정 그대로 실측이 필요하다. 다만 10cm 에는 위쪽 한계가 있다. 60cm 이상으로 두면 AND 가 아무것도 안 거른다 — 첫째 시계가 울린 창에서 봇은 60cm 미만만 움직였으니 목적지에 그 이상 다가갈 수가 없고, 둘째 시계는 거의 늘 같이 울린다.

위 (2026-09 기준) 판단은 절반만 맞았다. 「후보와 확정을 가르는 일은 탈출 시도가 이미 한다」고 적었는데, 초당 7.5cm 로 목적지에 기어가는 엔진을 테스트로 만들어 보니 게이트를 끄면 끼임 후보 → 탈출 4회 실패 → Stuck 이었다(주행 시작 약 6초 뒤). 기어가는 봇은 옆으로도 기어서 2m 를 못 벗어난다. 켜면 WALK 를 유지하다 목적지 예산 20초가 끝날 때 Unreachable 로 남는다. 탈출 시도가 거르는 것(비켜 갈 수 있는 막힘)과 AND 가 거르는 것(느리지만 나아가는 중)은 다른 것이었다.

테스트 133 → 144. 시뮬 60칸: 켜고 끔 모두 끼임 후보 2회, 발견물 종류도 같다 — 시뮬에는 기어가는 경우가 없다. 달라진 것은 둘이다. 후보가 0.117초(7틱) 늦게 나고, Stuck 기록 좌표가 봇이 실제로 멈춘 자리로 55cm 옮겨졌다 — 끄면 창의 첫 샘플이 멈추기 직전 자리였다(발견물 원장이 같은 이슈로 묶는 1,500cm 반경 안이다). 실게임 주행은 아직이다.

이 편에서 값을 치른 교훈은 이겁니다 — 같은 문제를 이미 풀고 있어도, 남이 한 겹 더 쌓아 둔 것이 보입니다. 저는 끼임을 시간으로 판정하고 있었고 그게 틀린 건 아닙니다. 다만 후보를 잡는 층과 오탐을 걷는 층을 나누지 않았을 뿐입니다. 층을 나누자마자 오탐의 원인이 이름을 갖게 됐습니다 — “의도적으로 멈춘 정상 상태”.

그리고 ⚠ 도구가 대상을 얼마나 방해하는지를 같이 재는 습관은 그대로 가져올 만합니다. 제 QA 자동화도 게임 안에서 돌면서 게임을 느리게 만들 수 있는데, 그걸 재본 적이 없습니다.