DEV Community

김이더
김이더

Posted on

Jev AI 를 게임 3개에 붙여본 결과 — 2번 실패, 1번 성공

코드는 GitHub LAMDiceBot·deguri·CrazyArcadeCopy에, 앱은 lamdice.com과 deguri.ai.kr에서 볼 수 있다.
더 많은 글은 radarlog.kr에서.


AI 를 붙였더니 봇이 더 못해졌다.

처치율 67% 에서 53%. 2,840번 호출에 실패는 1번, 비용은 $0.054. 돈은 거의 안 들었다. 효과도 없었다.

이게 두 번째 실패였다. 첫 번째는 AI 가 조종하는 공이 벽에 끼어서 안 내려왔고, 세 번째에서야 "이건 된다" 싶은 자리를 찾았다.

게임 세 개에 같은 모델을 붙여 본 기록이다.

왜 하필 Jev 였나

시작은 쓰레드 반응이었다. "jev ai 를 게임에 접목" 글이 725 조회, "jev 로 게임 AI" 글이 2시간 만에 445. 게임 기능만 소개한 글은 100 을 못 넘었다.

사람들은 AI 가 판단하는 게 화면에 보이는 쪽에 반응했다.

Jev 는 TypeSafe 의 모델인데, 글을 쓰는 LLM 이 아니다. 상태(state)와 타입이 정해진 질문을 보내면 선택지 위의 확률만 돌아온다.

choice 는 "이 중 어느 것", score 는 "어느 단계", noul 은 "참인가". 파싱할 게 없고 코드가 바로 if 로 받는다.

curl -s https://api.typesafe.ai/v1/systemone \
  -H "Authorization: Bearer $TYPESAFE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"jev-latest",
       "state":"Help! My payouts have been failing for 3 days.",
       "questions":{"is_urgent":{"type":"noul","instructions":"Does this convey urgency?"}}}'
Enter fullscreen mode Exit fullscreen mode

돌아오는 건 "noul": 0.95 한 줄이다. 긴급할 확률 95%.

가격이 결정타였다. 입력 100만 토큰당 $0.042, 출력은 무료. 한 번 부르는 데 1원 안팎이라 판마다 수백 번 불러도 부담이 없다. 응답은 공식 문서가 "약 100ms", 내 실측은 205~285ms.

게임 프로그래머 눈에는 상태 머신에 꽂는 분기 하나로 보였다. 그래서 가장 실시간인 것부터 붙여 봤다. 거기서 첫 번째 실패가 나왔다.

1차: 타임어택 봇 — 공이 벽에 끼었다

데구리 단독판의 타임어택은 픽셀 동물 공이 언덕을 굴러 내려오는 모드다. 조작은 기울기와 점프뿐, 독수리한테 잡히면 시간을 잃는다.

여기에 Jev 를 조종사로 앉혔다. 120ms 마다 상태를 영어 문장으로 보내고, 기울기 여섯 개 중 하나(choice)와 점프할지(noul)를 받았다.

첫 판부터 공이 오른쪽 벽에 붙어서 안 내려왔다.

원인은 보기 문구였다. 구르기와 걷기에서 기울기의 뜻이 다른데, 걷기용 설명 "통로에서 가장 빠름"이 구르기 보기에 섞여 있었다.

Jev 는 그걸 문자 그대로 읽고 hard_right 만 골랐다.

// Before — 걷기용 설명이 구르기 보기에 섞임
hard_right: "fastest in the corridor"

// After — 모드별로 설명을 나누고, 사실만 적는다
rolling.hard_right: "tilt hard toward the right wall"
walking.hard_right: "walk right along the corridor"
Enter fullscreen mode Exit fullscreen mode

"가장 빠름" 같은 평가 낱말이 선택을 끌고 간다. 설명을 모드별로 나누고, 상태에 "최근 2초 거의 안 내려감(끼었을 것)"을 넣어서야 벽에서 떨어졌다.

고치고 나서 5시드 평균 51.7초. 그런데 가만히 두면 58.1초, 독수리 올 때만 점프하는 규칙 한 줄이면 44.3초였다.

AI 가 규칙 한 줄을 못 이겼다. 독수리한테는 매 판 잡혔다.

요청은 120ms 마다 가는데 응답이 205ms 다. 답이 오면 이미 다음 상황이다. 게다가 Jev 는 타이밍 수치에 약해서, 독수리가 잡기 1.2~0.85초 전에 미리 뛰고 재사용 대기에 걸렸다.

그래서 역할을 바꿨다. 코드가 기울기×점프 후보를 1.5초 앞까지 미리 굴려 결과를 말로 적어 주고, Jev 는 그 중에서 고르기만 한다.

candidate A: reaches the next section, eagle still far away
candidate B: hits the right wall and nearly stops
candidate C: jumps early, lands in the mud
Enter fullscreen mode Exit fullscreen mode

18.56초. 독수리를 전부 피했다. 같은 후보를 코드 점수로 고르면 20.86초, 운영 중 사람 1위가 31.1초(9명). 비용은 한 판에 수백 번 물어서 약 1센트.

사람 랭킹을 압도한 건 Jev 가 아니라 미리 굴리기다. Jev 는 코드 점수보다 2초 남짓 나았을 뿐이다. 후보 평가는 된다, 조종은 안 된다.

2차: 크아 봇 전술 — 위약 효과에 속을 뻔했다

두 번째는 크레이지 아케이드를 따라 만든 물풍선 게임이다. 봇은 0.1초마다 위험 지도를 다시 그려서 움직이고, 이미 꽤 잘한다.

Jev 한테는 전술만 맡겼다. 1.5초마다 상황을 문장으로 보내고 사냥·아이템·파밍 셋 중 하나를 받는다. 상태는 2,000자, 후보 설명은 200자로 잘랐다.

설계하던 날은 API 키가 없어서 Jev 를 한 번도 못 불렀다. 그런데도 "코드 점수 선택" 군 승률이 80%.

파 보니 첫 판단을 7ms 미룬 것만으로 기존 봇 승률이 1.7% 에서 38.3% 로 뛰었다. 14ms 면 93.3%.

왜 그런지는 아직 모른다. 전술이 아니라 타이밍 몇 ms 가 승률을 흔든 것만 분명했다.

위약 폭을 먼저 재지 않으면 어떤 숫자도 믿을 수 없다.

나흘 뒤 키를 받아 실측했다. 3맵×10시드, 고수 봇 처치율 67% 에서 53%. 2,840번 호출에 실패 1번, 응답 중앙값 285ms, 입력 128.9만 토큰, $0.054.

67% → 53%
2,840번 호출. $0.054. 14%p 하락.
Enter fullscreen mode Exit fullscreen mode

−14%p 는 위약 폭 10%p 안이라 "확실히 나쁘다"고는 못 한다. 하지만 나아진 증거도 없다. Jev 가 셋 중 자꾸 "아이템"을 골라 봇을 사냥에서 떼어 놓고 있었다.

코드는 0.1초마다 위험 지도를 보는데 Jev 는 1.5초마다 문장 몇 줄만 본다. 정보가 적은 쪽이 이길 수 없다. 코드가 이미 잘하는 결정에 AI 를 얹은 게 잘못이었다.

그래서 자리를 옮겼다. 선수가 아니라 감독이다. 30초마다 난이도를 up/keep/down 으로, 봇 성향을 choice 로, 10초마다 작전 하나를 받는다.

틱 단위 결정은 코드가 그대로 쥔다. 이건 지금 운영 중이다.

3차: 데구리 하이라이트 — 됐다, 근데 끝 장면만 골랐다

세 번째는 실시간이 아니라 이미 끝난 경주다.

경주가 끝나면 서버가 6초짜리 하이라이트 후보를 만든다. 역전, 독수리 납치, 구멍, 진흙, 낮잠, 몸싸움, 접전.

후보마다 사실을 숫자로 모아 검사한 뒤 영어 문장으로 다시 쓴다. 참가자 이름, 방 이름, 좌표는 안 보낸다. 순번과 동물과 사건만.

그걸 choice 하나로 묻는다.

{
  "model": "jev-latest",
  "state": {
    "task": "Choose the best 6-second highlight of a finished race.",
    "race": "8 pixel animals rolled down a hill. The rabbit led most of the way...",
    "what_makes_it_fun": "The moment the outcome flips is the core. Everyone already saw the finish."
  },
  "questions": {
    "pick": {
      "type": "choice",
      "instructions": "Which clip would be the most fun to watch?",
      "criteria": {
        "c1": "the leader was snatched by the eagle",
        "c2": "nothing special happened"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode
{
  "model": "jev-1.13.0",
  "answers": {
    "pick": {
      "type": "choice",
      "choice": "c1",
      "probabilities": { "c1": 0.88, "c2": 0.12 },
      "confidence": 0.76
    }
  },
  "usage": { "input_tokens": 318, "output_tokens": 34 }
}
Enter fullscreen mode Exit fullscreen mode

됐다. 결과 카드에 그 장면이 GIF 로 뜬다.

그런데 20판 돌려 보니 18판이 끝 장면이었다.

후보 문장에 "very end of the race" 같은 위치 단서가 있었다. 1차의 "가장 빠름"과 똑같은 함정이다.

// Before — 위치 단서가 선택을 끌어간다
"the rabbit took the lead at the very end of the race"

// After — 위치를 말하지 않는다
"the rabbit overtook the leader after trailing for most of the race"
Enter fullscreen mode Exit fullscreen mode

위치를 지우고, 코드가 "결정적 계기" 후보를 항상 하나 넣게 했다. 그 뒤로는 접전이었던 판에서만 결정 장면을 고른다.

써 본 사람들은 결정되는 순간만큼 접전이 재밌다고 했다. 그래서 붙어 간 시간과 골인 차이도 후보에 넣었다.

단 숫자가 아니라 낱말로. neckSec: 4 대신 "stayed neck and neck for several seconds". Jev 는 수치 비교에 약하다.

반응도 학습에 쓴다. 결과 카드의 👍/👎 를 사실별 좋아요 비율로 모아 코드 점수 가중치를 0.5~2배로 조정한다. 그리고 5판에 1판은 코드 점수로만 골라 Jev 가 정말 나은지 계속 비교한다.

다른 프로젝트에 가져갈 것

세 저장소의 호출 코드를 함수 하나로 줄였다. 키 없음, 실패, 시간 초과, 엉뚱한 답은 전부 null. 부르는 쪽이 코드 규칙으로 대신한다.

const JEV_TIMEOUT_MS = 3000;

async function askJev(state, questions) {
    const key = process.env.TYPESAFE_API_KEY;
    if (!key) return null;
    try {
        const res = await fetch('https://api.typesafe.ai/v1/systemone', {
            method: 'POST',
            headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
            body: JSON.stringify({ model: 'jev-latest', state, questions }),
            signal: AbortSignal.timeout(JEV_TIMEOUT_MS)
        });
        const body = await res.json().catch(() => null);
        if (!res.ok || !body || !body.answers) return null;
        return body.answers;
    } catch (err) {
        console.warn('[jev] 실패:', err && err.name);
        return null;
    }
}

const answers = await askJev(state, { pick });
const pick = answers && ['c1', 'c2'].includes(answers.pick.choice)
    ? answers.pick.choice
    : codeFallback();
Enter fullscreen mode Exit fullscreen mode

choice 가 내가 준 옵션인지 다시 검사하고, 타임아웃은 2~3.5초. 실시간 경로면 재시도 없이 바로 fallback 이다.

원칙은 세 번이 전부 같은 걸 가리켰다. 흐름은 코드가 쥐고 Jev 는 좁은 판단 하나만. 숫자는 코드가 계산하고 Jev 에는 낱말로.

질문 하나에 판단 하나, 여러 질문은 한 요청에 묶는다. 병렬로 평가되니 시간은 그대로고 비용은 토큰만큼만 든다.

키는 서버 환경변수에서만 읽고, 상한을 둔다. 소켓당 동시 요청 1개, 분당 60회, 하이라이트는 판당 1회. 'Jev' 배지는 실제로 Jev 가 답했을 때만 붙인다.

그리고 비교 실험은 위약군부터. 7ms 가 승률을 1.7% 에서 38.3% 로 바꾸는 세계에서, 대조군 없는 숫자는 숫자가 아니다.

세 번 다 같은 질문이었다. 이 결정을 코드가 이미 잘하고 있나. 그렇다면 AI 는 안 얹는다.

코드가 못 하는 "이 중 뭐가 더 볼만한가"는 이미 일어난 일에 물을 때 가장 잘 맞았다.

"AI 는 선수가 아니라 감독 자리에 앉혀라. 끝난 경기를 보는 자리면 더 좋다."

Top comments (0)