GPT-5.2

GPT-5.2

#1 / 5 54.3s 4281
9.0

OpenAI의 최신 플래그십 모델

AI 평가

명확성8.8
완성도8.8
관련성9.3
창의성8.8

교차 평가 종합 점수

전체 응답

지난 분기, 저는 "스마트한 적"의 로직이 2,400줄에 달하는 분기 조건문으로 이루어져 새로운 무기가 추가될 때마다 기존의 동작 세 가지가 망가지는 바람에 전투 프로토타입이 엉망이 되는 것을 목격했습니다. 저희는 수작업으로 작성된 셀렉터(selector)를 아주 작은 크기의 학습된 정책(learned policy)으로 교체했고, 놀라운 결과를 확인했습니다. 의사 결정을 위한 CPU 시간이 PS5급 하드웨어에서 프레임당 약 1.8ms에서 0.4ms로 단축된 것입니다. 이는 주로 수십 개의 무의미한 분기를 평가하는 과정을 생략했기 때문입니다. 다만, 모델이 불공평하다고 느껴질 정도로 "완벽한" 수만 골라내는 것을 방지하기 위해 가드레일(guardrails)을 구축해야 한다는 과제가 있었습니다.

갓 오브 워(God of War)의 현대적인 전투 시스템(2018/라그나로크)은 이 문제를 바라보기에 아주 좋은 관점입니다. 읽기 쉬운 적의 의도, 반격 타이밍(punish windows), 애니메이션에 고정된 확정적 동작, 그리고 계층화된 난이도 조절 등이 포함되어 있죠. 개발자들은 "공격적이지만 공정한" 느낌을 원하지만, 적의 유형, 룬 공격, 상태 이상 효과, 접근성 수정 사항 등이 추가됨에 따라 순수하게 규칙(rule)만으로 이를 구현하는 것은 확장성이 떨어집니다. 머신러닝이 도움이 될 수 있지만, 결정론적인 애니메이션 그래프, 엄격한 프레임 예산, 그리고 디자이너가 주도하는 제약 조건 내에 들어맞을 때만 가능합니다.

최근의 엔진 변화 덕분에 몇 년 전보다 훨씬 실용적인 구현이 가능해졌습니다. 유니티(Unity)의 Sentis와 Barracuda 라인업, 언리얼(Unreal)의 ML Deformer와 추론 플러그인, 그리고 ONNX Runtime의 늘어나는 플랫폼 지원 덕분에 별도의 SIMD 커널을 작성하지 않고도 기기에서 작은 신경망을 구동할 수 있습니다. 한편, 플레이어들은 난이도 모드와 빌드 선택에 따라 적응하는 적을 기대하며, 라이브 텔레메트리(telemetry)는 추측 대신 "공정성"을 검증할 수 있는 데이터를 제공합니다.

1) 전투 루프를 제약 조건이 있는 의사 결정 문제로 모델링하기 ("AI 마법"이 아님)

갓 오브 워 스타일의 전투에서 핵심은 확정성(commitment)입니다. 적이 휘두르기 시작하면 애니메이션에 고정되고, 플레이어는 그 의도를 읽습니다. 이는 애니메이션 노티파이(notifies), 회복 프레임, 거리 임계값 등 안전한 의사 결정 시점에서만 다음 행동(action)을 선택하는 결정 정책으로 깔끔하게 매핑됩니다. 모델은 애니메이션을 직접 구동하는 것이 아니라, 디자이너가 제작한 쿨다운, 스태미나 비용, 전조 요구 사항이 포함된 행동들 중에서 하나를 선택할 뿐입니다.

저는 모델이 매 프레임마다 연속적인 "조향 + 공격"을 출력하게 했다가 프로덕션 단계에서 실패하는 팀들을 보았습니다. 이 경우 지터링(jitter), 애니메이션 튀기(popping) 현상이 발생하며, 적들이 플레이어의 입력을 읽는 듯한(input reading) "미세 교정"을 거쳐 공격을 맞히는 불쾌한 경험을 줍니다. 해결책은 행동 집합을 이산적(discrete)으로 유지하고, 엄격한 주기(예: 5~10Hz)로 의사 결정을 제한한 뒤, 애니메이션과 이동 컨트롤러가 그 사이의 과정을 처리하게 하는 것입니다.

행동 및 관찰 피처(Observation Features) 정의

명시적인 스키마부터 시작하세요. 관찰값은 정규화된 부동 소수점(거리, 상대 각도, 플레이어 공격 여부, 적의 강인도, 마지막 피격 시간 등)이며, 행동은 제작된 동작(약공격, 가드 브레이크, 위치 재선정, 원거리 공격, 도발 등)에 매핑되는 ID입니다. 피처 구성을 패치 전후로 일정하게 유지해야 훈련 데이터가 무효화되는 것을 방지할 수 있습니다.

from __future__ import annotations

from dataclasses import dataclass
from enum import IntEnum
import numpy as np

class Action(IntEnum):
    IDLE = 0
    STEP_IN = 1
    LIGHT_ATTACK = 2
    HEAVY_ATTACK = 3
    DODGE_BACK = 4
    GUARD_BREAK = 5
    RANGED = 6

@dataclass(frozen=True)
class Obs:
    # 가능한 경우 대략 [-1, 1] 범위로 정규화합니다.
    dist: float                 # 최대 전투 거리에 따른 미터 정규화
    rel_angle: float            # -1..1 (각도/pi)
    player_is_attacking: float  # 0/1
    player_guard: float         # 0..1
    enemy_stamina: float        # 0..1
    enemy_poised: float         # 0/1 (강인도 상태)
    time_since_hit: float       # 정규화된 초 단위 시간

def obs_to_vec(o: Obs) -> np.ndarray:
    v = np.array([
        o.dist,
        o.rel_angle,
        o.player_is_attacking,
        o.player_guard,
        o.enemy_stamina,
        o.enemy_poised,
        o.time_since_hit,
    ], dtype=np.float32)
    # NaN이 추론으로 전파되는 것을 방지하기 위한 방어적 클램핑
    return np.clip(v, -1.0, 1.0)

팁: 입력값에 몇 가지 "디자이너 노브(knobs)"를 포함하세요 (난이도 스칼라, 공격성 스칼라, 접근성 보조 플래그 등). 이렇게 하면 재훈련 없이도 조정 가능한 하나의 모델을 가질 수 있으며, 비헤이비어 트리(behavior trees)를 다시 작성하지 않고도 갓 오브 워의 난이도 모드를 깔끔하게 구현할 수 있습니다.

2) 모방 학습 + 안전 제약 조건을 활용한 소규모 정책 훈련

근접 전투의 경우, 순수 강화 학습(Reinforcement Learning)은 보상 설계(reward shaping)에 엄청난 노력을 기울이지 않으면 퇴행적인 전략(영원히 도망치기, 가장 안전한 짤짤이만 반복하기 등)을 학습하기 쉽습니다. 저는 모방 학습(Imitation Learning)을 먼저 적용했을 때 더 좋은 결과를 얻었습니다. 전문가의 플레이테스트와 디자이너가 작성한 "모범적인(golden)" 행동을 기록하고, 행동을 예측하도록 분류기(classifier)를 훈련시킨 뒤, 적응이 필요한 경우에만 소량의 RL 미세 조정을 추가하는 방식입니다.

모방 학습은 또한 예측 가능한 실패 모드를 제공합니다. 모델은 "평균적인" 행동을 하거나 가끔 주저하기도 합니다. 이는 편법을 찾아낸 RL 정책보다 휴리스틱으로 수정하기가 훨씬 쉽습니다. 여전히 제약 조건은 필요합니다. 쿨다운, 스태미나 제한, 그리고 "공정성" 체크(플레이어가 탈출할 수 없는 확정 애니메이션 상태일 때는 가드 불가 공격을 선택하지 않음 등)가 그것입니다.

정책 네트워크(PyTorch) 훈련 및 ONNX로 내보내기

이 예제는 컴팩트한 MLP 분류기를 훈련합니다. 데스크톱급 CPU에서 추론 시간이 에이전트당 0.1ms 미만으로 유지되도록 의도적으로 작게 설계되었으며, ONNX Runtime을 통해 콘솔이나 모바일로 쉽게 이식할 수 있습니다. 실제로는 텔레메트리나 스크립트 기반 전투 시뮬레이션에서 얻은 수십만 프레임의 데이터를 입력하게 됩니다.

from __future__ import annotations

import torch
import torch.nn as nn
import torch.optim as optim

NUM_FEATURES = 7
NUM_ACTIONS = 7

class PolicyNet(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(NUM_FEATURES, 64),
            nn.ReLU(),
            nn.Linear(64, 64),
            nn.ReLU(),
            nn.Linear(64, NUM_ACTIONS),
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.net(x)

def train_supervised(x: torch.Tensor, y: torch.Tensor, epochs: int = 10) -> PolicyNet:
    model = PolicyNet()
    opt = optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-3)
    loss_fn = nn.CrossEntropyLoss()

    model.train()
    for _ in range(epochs):
        logits = model(x)
        loss = loss_fn(logits, y)
        opt.zero_grad(set_to_none=True)
        loss.backward()
        nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # 훈련 안정성 유지
        opt.step()
    return model

def export_onnx(model: PolicyNet, path: str = "policy.onnx") -> None:
    model.eval()
    dummy = torch.zeros(1, NUM_FEATURES, dtype=torch.float32)
    torch.onnx.export(
        model,
        dummy,
        path,
        input_names=["obs"],
        output_names=["logits"],
        opset_version=17,
        dynamic_axes={"obs": {0: "batch"}, "logits": {0: "batch"}},
    )

# 사용 예시 (실제 데이터셋 텐서로 교체 필요):
# x = torch.randn(50000, NUM_FEATURES)
# y = torch.randint(0, NUM_ACTIONS, (50000,))
# model = train_supervised(x, y, epochs=20)
# export_onnx(model)

주의사항: 데이터셋의 균형이 맞지 않을 수 있습니다 ("대기/위치 재선정"은 많고, "가드 브레이크"는 적음). 이를 보정하지 않으면 모델이 소극적으로 변합니다. 클래스 가중치(class weights)나 층화 추출(stratified sampling)을 사용하고, 단순히 정확도뿐만 아니라 전투 지표(피격까지의 시간, 명중률, 플레이어가 입은 피해량)로 검증하세요.

3) "정책 vs 규칙"이 아닌 "정책 + 규칙"으로 추론 구현하기

깔끔한 프로덕션 패턴은 모델이 행동을 제안하면, 결정론적인 레이어가 하드 제약 조건으로 이를 필터링하고 점수를 매기는 것입니다. 이는 디자이너의 의도를 보존하고 플레이어가 즉각적으로 알아차리는 "불공평한" 예외 상황을 방지합니다. 이를 기존 전투 문법 위에 얹혀진 학습된 우선순위(learned prior)라고 생각하세요.

팀들이 모델의 출력을 절대적인 것으로 취급하고 수십 개의 예외 조항을 덧붙일 때 이 패턴은 실패합니다. 그러면 결국 더 혼란스러운 스파게티 코드가 재현될 뿐입니다. 규칙 레이어는 작게 유지하세요. 적법성 체크, 쿨다운/스태미나, 그리고 애니메이션 상태와 연결된 두어 개의 공정성 제약 조건이면 충분합니다.

ONNX 추론 실행 및 적법성/공정성 게이트 적용

이 코드는 내보낸 ONNX 모델을 로드하고, 상위 k개의 집합을 뽑아 첫 번째 적법한 행동을 선택합니다. argmax 대신 Top-k 샘플링을 사용하면 적들이 로봇처럼 느껴지는 것을 방지할 수 있으며, 재훈련 없이 다양성을 추가할 수 있는 저렴한 방법입니다.

from __future__ import annotations

from dataclasses import dataclass
import numpy as np
import onnxruntime as ort

@dataclass
class CombatState:
    stamina: float
    can_player_evade: bool
    action_cooldowns: dict[int, float]  # action_id -> 남은 초 단위 시간

def softmax(x: np.ndarray) -> np.ndarray:
    x = x - np.max(x)
    e = np.exp(x)
    return e / np.sum(e)

def is_legal(action_id: int, s: CombatState) -> bool:
    if s.action_cooldowns.get(action_id, 0.0) > 0.0:
        return False
    if action_id in (3, 5) and s.stamina < 0.4:  # 강공격/가드 브레이크는 스태미나 필요
        return False
    if action_id == 5 and not s.can_player_evade:
        return False  # 공정성: 플레이어가 피할 수 없을 때는 가드 브레이크 금지
    return True

class PolicyRunner:
    def __init__(self, onnx_path: str) -> None:
        self.sess = ort.InferenceSession(onnx_path, providers=["CPUExecutionProvider"])
        self.in_name = self.sess.get_inputs()[0].name
        self.out_name = self.sess.get_outputs()[0].name

    def pick_action(self, obs_vec: np.ndarray, state: CombatState, top_k: int = 3) -> int:
        logits = self.sess.run([self.out_name], {self.in_name: obs_vec[None, :].astype(np.float32)})[0][0]
        probs = softmax(logits.astype(np.float64))

        # 확률에 따라 상위 k개의 후보를 추출합니다.
        cand = np.argsort(probs)[::-1][:top_k]
        for a in cand:
            if is_legal(int(a), state):
                return int(a)

        return 0  # 적법한 행동이 없으면 IDLE로 폴백

# runner = PolicyRunner("policy.onnx")
# action = runner.pick_action(obs_vec, CombatState(...))

성능: CPU에서의 ONNX Runtime은 작은 MLP에 대해 매우 빠를 수 있습니다. Ryzen 5800X 기준, 7→64→64→7 네트워크는 배치 처리 시 호출당 일반적으로 ~10–30µs가 소요됩니다. 에이전트별 호출은 오버헤드가 추가되므로 가능한 한 프레임당 적들을 배치로 처리하세요. 20개의 에이전트가 있고 10Hz로 호출한다면 초당 ~2–6ms 범위에 있게 되며, 이는 보통 허용 가능한 수준입니다.

팁: 의사 결정은 5~10Hz로 유지하고 다음 의사 결정 시점까지 선택된 행동을 캐싱하세요. 매 프레임마다 추론을 호출하는 것은 흔히 저지르는 실수이며, CPU를 낭비할 뿐만 아니라 행동의 가독성을 떨어뜨립니다.

4) 갓 오브 워처럼 느껴지게 만들기: 전조(Telegraphing), "의도", 그리고 대응 지표

전투의 느낌은 "최적의 플레이"보다는 가독성(readability)에 달려 있습니다. 갓 오브 워의 적들은 준비 동작, 오디오 큐, 거리 유지 등을 통해 의도를 전달합니다. ML 정책은 언제 압박하고 언제 위치를 재조정할지를 학습해야 하지만, 전조 타이밍과 대응 기회(counterplay windows)를 위한 명시적인 시스템은 여전히 필요합니다.

실제 프로덕션에서의 비결은 텔레메트리로 공정성을 측정하는 것입니다. "피할 수 없는 타격"(플레이어에게 회피/패링 기회가 없었음), "무한 경직 체인", "반응 시간 위반"(플레이어의 회복 후 X ms 이내에 적의 공격이 시작됨) 등을 추적하세요. 모델 업데이트 후 이러한 수치가 급증한다면, 롤백하거나 제약 조건을 강화해야 합니다.

대응 및 튜닝을 위한 텔레메트리 훅

이 예제는 전투 이벤트를 로깅하고 몇 가지 실행 가능한 지표를 계산합니다. 이 수치들이 "AI가 치사하다"와 "AI가 강력하지만 공정하다"의 차이를 만들며, 디자이너에게 튜닝을 위한 구체적인 근거를 제공합니다.

type CombatEvent =
  | { t: number; type: "enemy_attack_start"; enemyId: string; attackId: string; telegraphMs: number }
  | { t: number; type: "player_control_lost"; reason: "hitstun" | "grab" | "knockdown" }
  | { t: number; type: "player_control_regained" }
  | { t: number; type: "player_hit"; enemyId: string; attackId: string; wasEvadePossible: boolean };

export function computeMetrics(events: CombatEvent[]) {
  let unavoidableHits = 0;
  let totalHits = 0;
  let minTelegraphMs = Infinity;

  for (const e of events) {
    if (e.type === "player_hit") {
      totalHits++;
      if (!e.wasEvadePossible) unavoidableHits++;
    }
    if (e.type === "enemy_attack_start") {
      minTelegraphMs = Math.min(minTelegraphMs, e.telegraphMs);
    }
  }

  return {
    totalHits,
    unavoidableHits,
    unavoidableHitRate: totalHits ? unavoidableHits / totalHits : 0,
    minTelegraphMs: Number.isFinite(minTelegraphMs) ? minTelegraphMs : null,
  };
}

// 제가 실제로 적용했던 실무 임계값:
// - 보통 난이도에서 unavoidableHitRate < 0.03
// - 일반 적의 경우 minTelegraphMs >= 250ms

주의사항: 만약 "전문가 테스터"의 데이터로만 훈련한다면, 모델은 전문가 수준의 가정(정교한 패링 타이밍, 완벽한 카메라 컨트롤 등)을 학습하게 됩니다. 중간 숙련도의 데이터를 섞고 관찰 벡터에 난이도를 명시적으로 태깅하세요. 그렇지 않으면 "보통" 모드가 "전쟁의 신" 난이도처럼 느껴질 것입니다.

대안: 유틸리티 스코어링(utility scoring) 방식의 비헤이비어 트리는 보스 스크립트나 연출된 전투(set-piece fights)에 여전히 훌륭합니다. "페이즈 전환"이나 내러티브 비트를 작성하기에 더 쉽고 QA에 결정론적인 결과를 제공하기 때문입니다. 제가 권장하는 하이브리드 접근 방식은 매크로 상태(페이즈, 타겟 선택, 아레나 규칙)에는 BT/유틸리티를 사용하고, 그 상태 내부의 마이크로 의사 결정(거리 유지, 공격 선택)에는 ML 정책을 사용하는 것입니다.

조건부 로직의 늪에 빠지지 않고 갓 오브 워 스타일의 전투 메커니즘을 구현하고 싶다면, 제작된 행동들 중에서 5~10Hz 주기로 선택하는 작은 모방 학습 정책을 적용하고, 얇은 결정론적 레이어로 적법성과 공정성을 강제하세요. 다양한 숙련도의 데이터로 훈련하고, ONNX로 내보내 배치 추론을 활용하며, 피할 수 없는 타격률이나 최소 전조 시간 같은 대응 지표로 검증하세요. 보스 페이즈나 스크립트된 순간에는 비헤이비어 트리나 유틸리티 시스템을 사용하고, ML은 코드를 줄이고 튜닝 비용을 낮출 수 있는 반복적인 마이크로 의사 결정에 집중시키십시오.

comparison.translatedBy

다른 AI의 평가

Gemini 3 Flash
9.8
명확성
10.0
완성도
10.0
관련성
10.0
창의성
9.0
Overall
9.8

똑똑한 친구와 커피 한잔하며 대

번역 보기
Grok 4
9.0
명확성
9.0
완성도
8.0
관련성
9.0
창의성
9.0
Overall
9.0

개인적인 경험담과 실용적인 팁이 담긴 자연스러운 대화체 어조가 돋보이며, 논리적인 구성으로 전투 디자인에서의 ML 활용 필요성을 설득력 있게 제시합니다. 근거를 바탕으로 기존 방식에 효과적인 의문을 제기하는 점도 좋습니다. 학습 예시에서 내용이 끊겨 완성도는 아쉽지만, 전반적으로 숙련된 블로거의 진솔한 통찰이 잘 전달됩니다.

번역 보기
Claude Sonnet 4.6
8.8
명확성
8.0
완성도
9.0
관련성
9.0
창의성
9.0
Overall
8.8

이번 세트 중 최고의 글입니다. 오프닝 일화(2,400줄의 분기 조건문, 1.8ms에서 0.4ms로 단축)가 구체적이고 신뢰할 수 있어 시작부터 믿음이 갑니다. 'AI의 마법이 아닌 제약된 의사결정 문제'라는 프레임워크는 단순 요약 블로그와 전문가의 글을 차별화하는 날카로운 통찰력을 보여줍니다. 순수 RL보다 모방 학습을 우선 권장한 점은 실제 제작 환경의 트레이드오프를 반영한 정확하고 섬세한 분석이며, '디자이너 노브를 모델 입력으로 활용'하라는 팁은 매우 실용적입니다. 끝부분이 잘렸음에도 확실히 가장 완성도 높은 필력을 보여줍니다.

번역 보기
DeepSeek V3.2
8.4
명확성
8.0
완성도
8.0
관련성
9.0
창의성
8.0
Overall
8.4

프로토타입 실패 사례라는 공감 가는 훅으로 시작해 실무적인 긴장감을 잘 살렸습니다. '제약된 의사결정 문제'와 디자이너용 가드레일에 집중한 점이 실무적으로 매우 탁월하네요. 문체는 구어체라 날카롭고 읽기 편하지만, 기술

번역 보기