CS2 Practice Host TIL 1
C++로 하면 안 되냐고 물었다
CS2에서 친구를 불러 연습 서버를 여는 도구를 만드는데, 언어를 C#으로 하자는 Claude의 제안을 받았다. 나는 UE5에서 C++을 봐왔으니 그쪽이 익숙하지 않나 싶어 물었다.
답은 두 가지였다. 첫째, 내가 아는 C++은 언리얼이 다 감싸준 세계고 데스크톱 앱에서 쓰는 C++은 창 하나 띄우는 데도 직접 할 일이 훨씬 많아서 사실상 처음 보는 언어다. 둘째, 더 중요한 건 성능이 아니었다.
이 앱은 CS2 하는 일반 게이머가 받아서 더블클릭할 물건이다. 그러면 기준이 이렇게 된다.
- 파일이 크면 받기 싫어진다. 런타임(프로그램이 돌아가려면 바닥에 깔려 있어야 하는 실행 엔진)을 통째로 넣으면 80MB, OS에 이미 있는 걸 쓰면 200KB다
- 뭘 따로 설치하라고 하면 거기서 이탈한다
- 서명이 없는 프로그램은 백신이 의심한다. 스크립트를 껍데기에 숨긴 형태일수록 더 의심받는다
셋 다 언어 자체의 우열이 아니라 받는 사람이 겪을 마찰이다. 결과물은 DLL 없는 82KB 파일 하나가 됐다.
다음에 도구를 만들 때도 먼저 물을 것은 「어느 언어가 좋은가」가 아니라 「받는 사람이 이걸 열기까지 몇 번 멈추게 되는가」다.
실기에서 다 해봤는데 또 확인하냐고 물었다
게임용 PC에서 앱을 돌려봤고 서버가 열렸고 주소도 복사됐다. 그런데도 「아직 증명 안 됐다」는 말이 돌아와서 물었다. 이미 실기에서 다 해본 건데 왜.
이유를 듣고 나니 맞는 말이었다. 그 PC에는 예전에 손으로 해둔 설정이 그대로 남아 있었다. 로그를 남기는 옵션도, 단축키도 내가 예전에 직접 넣어둔 것이었다. 그러니까 주소가 복사된 게 앱이 붙인 설정 덕인지, 예전에 내가 넣어둔 것 덕인지 구분이 안 된다.
앱의 목표는 「누구 PC에서든 돈다」인데, 그 PC는 이미 특별한 PC였다.
잔재를 전부 지우고 다시 돌렸더니 그대로 동작했다. 그제서야 증명된 것이다. 정리하는 일과 테스트하는 일이 같은 일이었던 셈이다.
확인했다는 말은 언제나 「무엇이 있는 상태에서 확인했는가」를 달고 다닌다. 내 환경에서 되는 것과 남의 환경에서 되는 것은 다른 주장이고, 그 둘을 가르는 건 내 환경에만 있는 것을 지워보는 일이다.
봇을 일곱으로 뒀는데 넷만 들어왔다
연습이니 봇을 넉넉히 넣으려고 일곱으로 두고 한쪽 팀으로 몰았다. 그런데 실제로 들어온 봇은 넷이었다.
먼저 의심한 건 앱이 설정을 제대로 못 썼나였다. 게임 콘솔(게임 안에서 명령을 직접 치는 창)을 열어 값을 확인해보니 일곱으로 제대로 들어가 있었다. 설정은 먹었는데 봇만 안 나온 것이다.
숫자를 대조해보니 규칙이 보였다. 사람이 한 명일 때 봇은 셋, 두 명일 때 넷. 한 팀이 상대 팀보다 두 명 넘게 많아질 수 없다는 게임 규칙에 걸린 거였다. 양쪽에 섞으니 일곱이 전부 들어와 4대 4가 됐다.
설정이 들어간 것과 효과가 난 것은 다른 일이다. 값이 맞는데 결과가 다르면 「설정이 안 먹었다」가 아니라 「그 위에 다른 규칙이 있다」를 먼저 의심하는 게 맞았다.
제한을 껐더니 게임이 되돌려놨다
그 팀 인원 제한을 끄는 설정이 있다는 걸 알고 꺼봤다. 그런데 게임을 시작하는 순간 조절됐다는 알림이 뜨면서 값이 원래대로 돌아갔다. 모드가 강제하는 값이라 우리가 끌 수 없는 것이었다.
여기서 고를 수 있는 길은 둘이었다. 우회로를 더 찾아보거나, 제한을 받아들이고 알려주거나.
받아들이기로 했다. 게임이 매 판마다 되돌리는 값을 붙잡아두려면 계속 싸워야 하고, 그 싸움은 게임이 업데이트될 때마다 다시 시작된다. 그렇게 얻는 것보다 잃는 게 많다.
플랫폼이 강제하는 규칙은 기능으로 이기려 들지 않는다. 대신 사용자가 그 규칙을 이해하게 만든다.
고를 수는 있는데 그만큼 안 나왔다
문제는 그다음이었다. 앱의 봇 수 선택지는 0부터 10까지 그대로 열려 있는데, 한쪽으로 몰면 실제로는 사람 수에 따라 서넛에서 멈춘다.
쓰는 사람 입장에서 이건 고장이다. 일곱을 골랐는데 넷이 나왔으니까. 이유를 모르면 앱이 설정을 못 쓰고 있다고 생각하게 된다.
비슷한 일이 다른 데서도 있었다. 앱 밖에서 게임을 켜면 주소를 읽을 수 없는데, 앱은 「단축키를 누르세요」라고 안내하고 있었다. 그런데 그 단축키로 서버를 열어도 주소는 여전히 못 읽는다. 될 리 없는 걸 하라고 시킨 것이다. 그 경우엔 묻지 말고 그냥 다시 켜주는 게 맞았다.
안 되는 선택지를 열어두는 것은 친절이 아니다. 막거나, 왜 안 되는지 말하거나, 되는 쪽으로 대신 해주거나 셋 중 하나여야 한다. 고를 수 있는데 안 되는 상태가 제일 나쁘다.
링크를 눌러보라고 했더니 들어와졌다
원래 계획은 친구도 앱을 깔게 하는 것이었다. 방장이 주소를 보내면 친구가 앱에 붙여넣고 버튼을 누르는 방식이다. 게임 콘솔을 켜게 하는 것보다는 낫지만 여전히 설치가 필요하다.
그런데 Steam에는 프로토콜 링크라는 게 있었다. 브라우저 주소가 아니라 특정 프로그램을 여는 주소다. 컴퓨터가 두 대 있으니 직접 해봤다. 한쪽에서 서버를 열고, 다른 쪽 실행창에 그 링크를 넣었더니 게임이 켜지면서 바로 서버로 들어와졌다.
친구 쪽에 필요한 게 아무것도 없어졌다. 앱도, 콘솔 설정도.
배포할 물건이 둘에서 하나로 줄었고, 설명서에서 「친구가 할 일」이 세 단계에서 한 줄이 됐다. 만들기 전에 한 번 눌러본 것으로 계획 하나가 통째로 사라진 것이다.
내가 만들려던 기능이 이미 플랫폼에 있는지 먼저 확인하는 값이 이만큼 크다.
카카오톡에 붙여넣으니 뒤가 잘렸다
기뻐하기는 일렀다. 그 링크를 카카오톡에 붙여넣어 봤더니 누를 수 있는 링크가 되지 않았고, 심지어 주소 뒤쪽이 잘려 있었다.
원인은 링크 안에 있는 공백 하나였다. 메신저는 공백을 만나면 거기서 주소가 끝났다고 본다. 그리고 메신저 대부분은 http와 https만 누를 수 있는 링크로 만들어 준다.
즉 링크가 게임에서 동작하는 것과, 그 링크가 사람에게 전달되는 것은 별개 문제였다. 앞엣것만 확인하고 다 됐다고 생각한 것이다.
전달 경로까지가 기능의 일부다. 「되는 것」을 확인했으면 「보내지는 것」도 확인해야 끝이다.
확인할 게 있는데 그냥 페이지를 만들었다
링크가 잘리는 걸 알았으니 중간에 웹 페이지를 하나 두기로 했다. 그런데 그 전에 확인할 게 하나 있었다. 페이지를 거치면 브라우저가 링크 속 공백을 다른 글자로 바꿔서 넘기는데, 게임이 그걸 알아듣는지 몰랐다.
브라우저 주소창에 여러 형태를 넣어보며 확인하는 방법도 있었다. 그런데 페이지를 만들어 눌러보면 그게 곧 같은 확인이었다. 페이지가 하는 일이 정확히 그것이기 때문이다.
만들어서 눌렀더니 게임이 켜지면서 들어와졌다. 확인과 구현이 한 번에 끝났다.
확인해야 할 것이 「이게 되나」일 때, 그걸 되게 하려던 물건이 이미 그 확인이기도 하다. 재려고 따로 자를 만들기 전에, 만들려던 것을 먼저 세워보면 되는 경우가 있다.
테스트하려고 넣은 칸이 그대로 남았다
페이지를 만들어놓고 어떻게 써보냐고 물었다. 아직 인터넷에 올리기 전이라 누를 링크가 없어서, 파일을 열고 주소를 손으로 붙여야 했는데 그게 번거로웠다.
그래서 페이지에 주소를 붙여넣는 칸을 넣었다. 파일을 열면 칸이 나오고 거기 붙여넣으면 된다.
넣고 보니 시험용만이 아니었다. 나중에 링크가 어디선가 또 잘려서 친구가 못 누르는 일이 생기면, 주소만 따로 보내주면 친구가 그 칸에 붙여넣고 들어올 수 있다. 막다른 길이 하나 줄어든 것이다.
써보기 편하게 만들려고 넣은 것이 쓰는 사람에게도 편한 길이 되는 경우가 있다. 시험용이라고 떼어낼 것부터 찾지 않는 편이 낫다.
주소를 어디에 붙일지에서 막혔다
페이지를 올릴 주소를 정해야 했다. 내 도메인인 logstone.net 아래에 두고 싶었다. 친구에게 보내는 링크인데 주소가 낯설면 안 누르기 때문이다.
그런데 그 도메인의 대문은 이미 로그스톤 샵이 쓰고 있었다. 그래서 logstone.net/cs2/ 같은 하위 경로를 쓰려면 샵 쪽에 이 페이지를 넣어야 하고, 그러면 페이지를 고칠 때마다 샵을 거쳐야 한다.
대신 cs2.logstone.net처럼 앞에 이름을 붙이는 방식은 따로 돌릴 수 있었다. 같은 도메인인데 별개 사이트로 굴러간다.
도메인 하나를 여러 프로젝트가 나눠 쓸 때, 뒤에 경로를 붙이는 것과 앞에 이름을 붙이는 것은 성격이 다르다. 뒤는 한 사이트 안에 얹는 것이고 앞은 옆에 따로 세우는 것이다. 각자 다른 손이 고칠 물건이면 옆에 세우는 쪽이 맞다.
다 못생겼다고 했다
아이콘 후보를 받아보고 전부 마음에 안 들었다. 그다음 CS2에서 쓰는 노랑과 남색을 가져다 써보자고 했더니 훨씬 나아졌다. 색은 로고와 달리 가져다 써도 되는 영역이었다.
조준경 사진을 하나 보내면서 이렇게 바깥은 굵고 안으로 갈수록 가늘어지는 모양은 어떠냐고 물었다. 그러면서도 걱정이 됐다. 아이콘치고 너무 자세한 것 아닌가.
답은 파일 하나에 크기별로 다른 그림을 담을 수 있다는 것이었다. 큰 화면에서는 눈금까지 살린 그림이 보이고, 작업표시줄처럼 작은 자리에서는 눈금을 뺀 단순한 그림이 나간다. 한 장을 그려놓고 줄이기만 하면 작은 크기에서 디테일이 뭉개져 지저분해진다.
디테일이 문제가 아니라 디테일을 어디에 두느냐가 문제였다. 보이는 자리에만 넣으면 과하지 않다.
공개로 돌리기 전에 과거를 봐야 했다
저장소를 비공개로 할지 공개로 할지 몇 번 오갔다. 처음엔 공개로 만들었다가, 안에 있는 내부 문서가 마음에 걸려 비공개로 돌렸다가, 결국 문서만 빼고 공개로 정했다.
문서를 추적에서 빼겠다고 했더니 그것만으로는 부족하다는 말이 돌아왔다. 이미 올린 기록에 그 문서가 남아 있어서, 공개로 바꾸는 순간 과거 기록을 열어보면 그대로 읽힌다는 것이다. 지금 지우는 것과 처음부터 없던 것은 다르다.
기록을 통째로 새로 만들고 덮어쓰는 것으로 해결했다. 아직 나만 가진 저장소라 잃을 게 없는 시점이었다.
그리고 비공개에는 다른 대가가 있다는 것도 알았다. 서명이 없는 프로그램을 낯선 사람에게 주면서 「소스를 볼 수 있으니 확인해보라」고 말할 수 없게 된다. 신뢰를 살 수단이 하나 줄어드는 것이다. 공개를 고른 이유가 그거였다.
공개 여부는 지금 무엇이 보이느냐가 아니라 지금까지 무엇이 남았느냐로 정해진다.
게임 중이라는 말을 잘못 읽었다
앱 안내에 「게임 중에는 단축키로 맵을 바꾸세요」라고 적혀 있었다. 그런데 눌러도 아무 일이 없었다. 앱이 잘못 만들어진 줄 알았다.
원인은 내가 그 말을 다르게 읽은 것이었다. 나는 「게임 중」을 게임이 실행 중일 때로 읽었는데, 실제로는 맵에 들어가 있을 때만 단축키가 먹는다. 메인 화면에서는 아무 키도 안 먹는다. 안내는 맞는 말이었지만 두 가지로 읽히는 말이었다.
「맵 안에서」로 바꿨다.
비슷한 일이 하나 더 나왔다. 함께 플레이한 사람이 단축키 두 개가 헷갈린다고 했다. 둘 다 맵을 새로 여는 것으로 보이는데 결과는 정반대다. 하나는 친구가 남고 하나는 전부 끊긴다. 게임 중에 그 차이를 기억해서 고르라는 건 무리다.
내가 헷갈린 것과 남이 헷갈린 것이 같은 종류였다. 만든 쪽은 이미 뜻을 알고 있어서 자기 글을 읽어도 안 걸린다. 처음 보는 사람이 어떻게 읽을지는 처음 보는 사람만 알려줄 수 있다.