베일스트라이크의 봇은 플레이어를 향해 걸어옵니다. 그런데 사이에 컨테이너가 있으면 컨테이너에 대고 걸었습니다.

길찾기가 없던 것은 아닙니다. 맵 안의 건물 하나에 통로용 점 여덟 개를 박아 두고 그 사이만 이었습니다. 그 건물 안에서는 그럴듯하게 돌아 나오는데, 마당에 놓인 컨테이너·모래주머니· 차량은 그래프에 아예 없으니 봇에게는 존재하지 않는 것이었습니다.

맵 전체를 격자로 굽습니다

맵을 고르는 순간 navigation.js가 그 맵의 실제 충돌 상자와 걸을 수 있는 면에서 0.5m 격자를 굽습니다. 손으로 점을 박지 않으니 맵이 여섯 개든 열 개든 같은 코드가 처리합니다.

이 게임의 맵은 평평하지 않아서 칸 하나에 높이가 여럿 필요합니다. 그래서 칸마다 바닥 높이를 최대 세 층까지 둡니다. 마당, 건물 안, 옥상, 서쪽 전망대가 같은 격자 안에 층으로 겹쳐 들어갑니다.

경로는 A*로 찾고, 찾은 뒤에 직선으로 갈 수 있는 구간을 합쳐 폅니다. 격자를 그대로 따라 걸으면 칸 모서리마다 꺾여 로봇처럼 보입니다.

두 가지를 더 붙였습니다.

  • 벽에 붙은 칸에는 요금을 매깁니다. 최단 경로만 보면 봇이 벽에 어깨를 문지르며 옵니다. 한 칸 띄워 도는 쪽이 조금 멀어도 사람처럼 보입니다
  • 눈앞이 트여 있으면 격자를 건너뜁니다. 마주 보고 있는데 격자를 계산할 이유가 없습니다

결과로 봇이 컨테이너를 돌아오고, 건물 안에 있으면 문을 찾아 나옵니다.

발을 딛는 판정은 한 점으로 하면 안 됩니다

경사로를 비스듬히 올라가면 봇이 가끔 허공을 짚었습니다. 딛고 올라설 높이를 진행 방향 한 점에서만 읽었기 때문입니다. 그 한 점이 경사로 옆 허공에 떨어지면, 몸의 대부분이 경사로 위에 있어도 바닥이 없다고 판단합니다.

지금은 몸 반지름 안의 다섯 점에서 높이를 골라 씁니다. 그리고 상자에 몸이 걸친 채로 굳는 경우를 대비해, 가장 얕게 걸친 면 쪽으로 프레임당 6cm씩 빠져나오게 했습니다.

반대 방향의 버그 — 플레이어는 상자를 뚫고 들어갔습니다

같은 날 정반대의 문제도 고쳤습니다. 옥상에서 뛰어내려 컨테이너 위로 떨어지면 플레이어가 상자 안으로 내려갔습니다.

원인은 "지지면을 무엇으로 보는가"였습니다. 발밑 계산이 걸어 다니라고 만든 면에서만 바닥을 찾고 있었습니다. 컨테이너 뚜껑은 걸어 다니라고 만든 면이 아니므로 바닥이 0으로 잡혔고, 그대로 통과해 내려간 것입니다.

여기서 갈라야 할 것이 있었습니다.

플레이어봇과 길찾기
막힌 상자의 윗면지지면으로 본다보지 않는다
이유위에서 떨어졌으면 그 위에 서야 한다못 올라가는 곳을 경로로 삼으면 안 된다

같은 세계인데 두 쪽이 다른 규칙을 씁니다. 봇에게 "상자 위도 바닥"이라고 알려 주면, 봇은 올라갈 방법이 없는 상자 위를 지나는 경로를 짜고 그 앞에서 멈춥니다.

땅에서 상자 위로 기어오르는 것은 여전히 안 됩니다. 천장 한도가 딛는 높이에 묶여 있어서 위에서 떨어졌을 때만 뚜껑 위에 섭니다. 탄피도 이제 상자 위에 떨어집니다.

검사가 게임과 같은 코드를 돌게

이 두 가지를 고치면서 발밑 처리를 footing.js로 따로 뺐습니다. 화면을 그리는 코드 안에 섞여 있으면 브라우저를 띄우지 않고는 확인할 수 없기 때문입니다.

지금 npm test가 43개를 봅니다. 그중 길찾기와 발밑 쪽만 적으면 이렇습니다.

  • 여섯 맵에서 봇 출발점 → 플레이어 도달, 건물 탈출, 컨테이너 우회
  • 경사로 → 옥상 → 전망대 등반
  • 맵마다 무작위 40곳, 총 240곳에 갈 수 있는가
  • 여섯 맵에서 무작위 낙하 360회 — 상자 안에 멈추거나 옆으로 밀려나지 않는가
  • 땅에서 기어오르기 불가, 점프로도 못 올라감, 옥상에서 마당까지 하강

그리고 예전 방식으로 되돌려 두 개가 실패하는 것을 확인했습니다. 검사가 실제로 그 결함을 잡는지는, 결함을 다시 넣어 봐야 압니다. 통과만 보고 넘어가면 아무것도 안 보는 검사와 구별되지 않습니다.

콘솔에 쌓이던 오류 하나

곁다리로 하나 더 정리했습니다. ESC로 멈췄다가 곧바로 복귀를 누르면 마우스 고정이 실패했는데, 브라우저가 잠금을 푼 직후 약 1.25초 동안은 다시 잠그는 것을 거부하기 때문입니다.

지금은 2.18초에 걸쳐 여섯 번 다시 요청하고, 그동안 버튼에 진행 중임을 띄웁니다. 그리고 거부는 정상 흐름이라 조용히 삼킵니다 — 예전에는 시도할 때마다 처리되지 않은 오류가 콘솔에 쌓였습니다. 정상적으로 일어나는 일을 오류로 두면, 진짜 오류가 그 사이에 묻힙니다.

/veilstrike/에서 바로 해 보실 수 있습니다. 맵 여섯 개와 무기 다섯 종, 클래식(적 4명·120초)과 서바이벌(5웨이브·300초)이 있습니다.