AI-Driven Game Testing and QA System: Turnkey Development
Manual QA testing doesn't scale: an open world with 1000+ quests and 100+ mechanics cannot be manually tested in reasonable time. A team of 5 testers covers about 2000 unique states per week, while our RL agent covers 10 million in a day. We developed a turnkey system that autonomously explores the game world, finds crashes, softlocks, exploits, and balance issues — working 24/7 and increasing coverage to 90% in hours.
Why Manual Testing Falls Short
Even a large QA team misses edge cases: rare action combinations, race conditions, quests in non-standard order. Our RL agent with curiosity-driven exploration (ICM) purposefully seeks unknown states, while the Go-Explore algorithm remembers interesting points and returns to them. In 10 million steps, the agent visits more unique states than a team of 5 in a month. As a result, we find 3x more bugs at launch, and QA costs drop by 10x.
How We Build the AI Testing System
Crash/Softlock Testing
The agent randomly explores the entire action space, triggering edge cases that crash the game or get stuck in a loop. Agent code with ICM:
class GameTestingAgent:
"""Agent for coverage-based testing"""
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 for new states
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) # high entropy for 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)
# log new states
state_hash = self._hash_state(obs)
self.visited_states.add(state_hash)
# detect softlock (agent goes in circles)
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()
Content Coverage Testing
We check if there are zones, items, or achievements never reached by normal gameplay. The agent with ICM is rewarded for visiting new states — hidden areas are detected automatically. On one project, we found 23 unimplemented quests and 5 hidden achievements that developers had forgotten about.
Exploit Detection
A special agent trained to maximize score without constraints:
# exploit reward: ONLY score, ignore all "normal" paths
def exploit_reward(info):
return info['score'] # + info['gold'] + info['level']
# train on minimal steps (fast exploit)
model = PPO("MlpPolicy", env,
gamma=0.5, # short horizon = wants fast rewards
ent_coef=0.1) # lots of exploration
# if agent finds a way to get 10x average score in 1 minute
# → this is an exploit for QA team
What Is Intrinsic Curiosity Module (ICM)?
More about ICM
ICM is a module that generates intrinsic rewards: the more the model errs in predicting the next state, the more curious the agent becomes. This drives it to discover new locations and mechanics. As noted in OpenAI Spinning Up, curiosity-driven exploration is key to efficient exploration in games with sparse rewards.
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 = how new this state is
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 # high for new, low for known
How Go-Explore Solves the Sparse Reward Problem
Classic RL gets stuck in games with sparse rewards. Go-Explore (Adept AI):
- Stores an archive of interesting states (by diversity).
- Randomly selects a state from the archive.
- Returns to it (deterministic replay).
- Continues exploration from there.
class GoExploreAgent:
def __init__(self, game):
self.game = game
self.archive = {} # cell -> (score, state_snapshot)
def cell_key(self, state):
"""Discretize state into cell (simplified for storage)"""
# for 2D game: (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):
# select a state from archive (rarely visited)
cell = self._select_cell()
state = self.archive[cell]['snapshot']
# restore state (savestate)
self.game.load_state(state)
# randomly explore N steps
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
Approach Comparison: ICM vs Random vs Expert
| Method | Coverage (states/hour) | Exploit detection | Setup time |
|---|---|---|---|
| Random | 10,000 | Low | 1 day |
| ICM | 500,000 | Medium | 2 weeks |
| Go-Explore | 1,000,000+ | High | 3 weeks |
What's Included in the Turnkey Package?
| Component | Description | Duration (weeks) |
|---|---|---|
| Crash/Softlock Agent | ICM + PPO, catches crashes and softlocks | 4 |
| Content Coverage | Checks coverage of zones, items, quests | 2 |
| Exploit Hunter | Agent trained to maximize score | 3 |
| Go-Explore | State archive integration for sparse rewards | 3 |
| CI/CD Integration | GitHub Actions / Jenkins, metrics dashboard | 2 |
| Documentation & Training | Agent descriptions, API, training your QA team | 1 |
Total: 12–16 weeks for a full system. Cost is calculated individually after auditing your project. Contact us for a consultation — we'll evaluate your game and propose a solution.
Efficiency Metrics
- State space coverage: % of game states visited (goal ≥90%).
- Crash count per build: average 3 crashes per build before optimization, 0 after.
- Softlock incidents found: 50+ per project.
- New exploits detected per patch: average 8.
- Time to 90% coverage: 4 to 8 hours (manually — a month).
Regression Testing
After a patch — launch agents to ensure previously passing tests are not broken. CI/CD pipeline automatically runs tests on each pull request:
# 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
Our engineers have 10+ years of experience in AI game development, with over 50 successful projects. We guarantee the system will find at least 3x more bugs than manual testing at launch. Get a consultation — we'll evaluate your game and propose a solution.







