AI-система тестирования и QA для игр
Ручное QA тестирование игры не масштабируется: открытый мир с 1000+ квестами, 100+ механиками не протестировать вручную за разумное время. RL агенты автономно исследуют игровой мир, находят краши, softlock'и, exploit'ы и balance issues — работая 24/7 быстрее любой QA команды.
Типы автоматизированного тестирования
Crash/Softlock тестирование: Агент случайно исследует всё пространство действий → триггерит edge cases, которые crash-ат игру или застревают в петле.
Content coverage testing: Есть ли zones/items/achievements, которые никогда не достигаются нормальным игровым путём?
Exploit detection: Агент, обученный максимизировать score любыми методами, найдёт sequence breaking и exploit стратегии быстрее QA тестировщика.
Regression testing: После патча — запустить агентов, убедиться что previously passing tests не сломались.
RL агент для автоматического исследования
class GameTestingAgent:
"""Агент для coverage-based тестирования"""
def __init__(self, game_env):
self.env = game_env
self.visited_states = set()
self.crashes = []
self.softlocks = []
# Curiosity-driven exploration
# ICM (Intrinsic Curiosity Module): reward за новые состояния
self.icm = ICM(obs_dim=game_env.obs_dim,
action_dim=game_env.action_dim)
self.policy = PPO("MlpPolicy", game_env,
ent_coef=0.05) # высокая entropy для exploration
def collect_coverage_data(self, n_steps=1_000_000):
obs = self.env.reset()
for step in range(n_steps):
try:
action, _ = self.policy.predict(obs)
obs, _, done, _, info = self.env.step(action)
# логируем новые состояния
state_hash = self._hash_state(obs)
self.visited_states.add(state_hash)
# детектируем softlock (агент ходит по кругу)
if self._detect_softlock():
self.softlocks.append(self.env.get_state_dump())
if done: obs = self.env.reset()
except Exception as e:
self.crashes.append({
'error': str(e),
'state': self.env.get_state_dump(),
'action_sequence': self.recent_actions[-100:]
})
obs = self.env.reset()
Intrinsic Curiosity Module (ICM)
Для coverage тестирования reward за исследование важнее внешней награды:
class ICM(nn.Module):
"""Intrinsic Curiosity: reward = prediction error for new states"""
def __init__(self, obs_dim, action_dim, feature_dim=256):
super().__init__()
# feature encoder
self.phi = nn.Sequential(
nn.Linear(obs_dim, feature_dim), nn.ELU(),
nn.Linear(feature_dim, feature_dim)
)
# forward model: predict next state features
self.forward_model = nn.Sequential(
nn.Linear(feature_dim + action_dim, feature_dim), nn.ELU(),
nn.Linear(feature_dim, feature_dim)
)
def intrinsic_reward(self, obs, action, next_obs):
phi_obs = self.phi(obs)
phi_next = self.phi(next_obs)
# prediction error = насколько новое это состояние
a_onehot = F.one_hot(action, self.action_dim).float()
predicted_next = self.forward_model(torch.cat([phi_obs, a_onehot], dim=1))
curiosity = F.mse_loss(predicted_next, phi_next.detach(), reduction='none').mean(-1)
return curiosity # высокий для новых, низкий для изученных
Go-Explore для игр с большим пространством
Классический RL застревает в sparse reward играх. Go-Explore (Adept AI):
- Сохраняет архив интересных состояний (по разнообразию)
- Случайно выбирает состояние из архива
- Возвращается к нему (deterministic replay)
- Продолжает исследование оттуда
class GoExploreAgent:
def __init__(self, game):
self.game = game
self.archive = {} # cell -> (score, state_snapshot)
def cell_key(self, state):
"""Discretize state into cell (упрощение для хранения)"""
# для 2D игры: (x//50, y//50, level_id)
return (state['x'] // 50, state['y'] // 50, state['level'])
def run(self, n_iterations):
for _ in range(n_iterations):
# выбрать состояние из архива (редко посещённое)
cell = self._select_cell()
state = self.archive[cell]['snapshot']
# восстановить состояние (savestate)
self.game.load_state(state)
# случайно исследовать N шагов
for _ in range(np.random.randint(5, 100)):
action = self.game.action_space.sample()
new_state, _, done, _, _ = self.game.step(action)
new_cell = self.cell_key(new_state)
if new_cell not in self.archive:
self.archive[new_cell] = {
'snapshot': self.game.save_state(),
'visits': 0
}
if done: break
Exploit Hunter агент
Специальный агент, обученный максимизировать score без ограничений:
# exploit reward: ТОЛЬКО score, игнорируем все "нормальные" пути
def exploit_reward(info):
return info['score'] # + info['gold'] + info['level']
# обучаем на минимальном числе шагов (быстрый эксплойт)
model = PPO("MlpPolicy", env,
gamma=0.5, # короткий горизонт = хочет быстрых наград
ent_coef=0.1) # много exploration
# если агент находит способ получить 10× средний score за 1 минуту
# → это exploit для QA команды
CI/CD интеграция
# GitHub Actions / Jenkins
name: Game QA Tests
on: [push, pull_request]
jobs:
ai-qa:
runs-on: ubuntu-latest
steps:
- name: Run Coverage Agent (10 min budget)
run: python run_coverage_agent.py --budget 600 --headless
- name: Check Coverage Metrics
run: |
python check_coverage.py \
--min-level-coverage 0.85 \
--max-new-crashes 0
Метрики QA системы:
- State space coverage: % игровых состояний посещённых
- Crash count per build
- Softlock incidents found
- New exploits detected per patch
- Time to 90% coverage: vs ручное тестирование (цель: 10× быстрее)
Сроки: 8–16 недель
Базовый crash/softlock агент с ICM — 4 недели. Полноценная QA система с Go-Explore, exploit hunter, CI/CD интеграцией, репортингом — 12–16 недель.







