はじめに
※生成AIで作成された記事です。
参考: TypeSafe AI は Jev のローンチ時に、DOOM をプレイする公式デモを公開しています。
本記事はその発想を自分で組み直し、「criteria(自然言語の行動指針)」「判断の可視化」
「自動探索ループ」を追加したものです。Jev API 自体は公式デモと同じものを使っています。
TypeSafe AI の Jev に古典 FPS「DOOM」をプレイさせる実験をしています。クラウド API 経由で 1 判断 250ms、家庭用 PC(GTX 970、RAM 16GB)でリアルタイムに動作します。
本記事では、30 秒の短い録画を例に、Jev がどう状態を受け取り、どう行動を選んでいるかを紹介します。

動作環境
| 項目 | 内容 |
|---|---|
| OS | Ubuntu 24.04 LTS |
| CPU | Intel Core i5 |
| GPU | NVIDIA GeForce GTX 970 (4GB VRAM) |
| RAM | 16GB |
| Python | 3.10.21 (conda env vizdoom) |
| ViZDoom | 1.3.1 |
| WAD | freedoom2.wad (MAP01, skill 3) |
構成
[ViZDoom] --(状態)--> [Python] --(テキスト)--> [Jev API]
↑ |
| (行動選択)
| ↓
[QUAD SYSTEM ログ] [ボタン入力]
-
ViZDoom 1.3.1 で DOOM を起動(
freedoom2.wad, MAP01, skill 3) - Jev API(TypeSafe AI)に状態をテキストで送信
- Jev が返した行動(
move_forward,attackなど)を ViZDoom のボタンに変換 - 1 判断あたり 8 tic(
frame_skip=8)進む
Jev に渡している状態テキストの例
You are playing DOOM. Your goal is to progress through the level and survive.
Balance offense and movement. Do not attack blindly when no enemy is visible.
Current state: var0=100, var1=0, var2=50, var3=0, red_mean=38.3,
enemy_visible=no enemy_count=0, front_blocked=no, took_damage=no,
damage_side=none, stuck_timeout=no,
open_left=far, open_center=far, open_right=far, item_visible=no
[Memory: visited=1cells, keys=none, max_dist=0, dead_ends=0]
各フィールドの意味は以下のとおりです。
| フィールド | 意味 |
|---|---|
var0 |
HP |
var1, var2, var3
|
ViZDoom のゲーム変数(武器、弾薬など) |
red_mean |
画面中央の赤成分平均(敵検出の補助) |
enemy_visible |
labels_buffer による敵検出 |
enemy_side |
敵が画面の左右どちらにいるか |
front_blocked |
前方が壁かどうか |
took_damage |
直前の判断以降に被弾したか |
damage_side |
被弾方向(left / right / back / front) |
stuck_timeout |
同じ場所に 80tic 以上留まっているか |
open_left/center/right |
深度バッファから算出した 3 方向の開け具合(far / mid / near) |
item_visible |
取得可能アイテムの検出 |
Memory |
訪問セル数、鍵、最大距離、行き止まり数 |
行動指針(criteria)も自然言語
Jev には「行動指針」も渡しています。たとえば move_forward には以下を指定:
Only advance when open_center=far or open_center=mid.
Do NOT advance if open_center=near;
instead turn toward the side that is far.
これで「前が塞がっていたら進まない」を表現できます。プロンプトエンジニアリングだけで行動を制御できるのが、この構成の面白いところです。
他の行動にも、それぞれ自然言語で条件を書いています。
"attack": "Fire whenever enemy_visible=yes. Do not wait for centering.",
"move_backward": "Retreat only when health < 30 AND enemy_visible=yes.",
"use": "Select 'use' whenever front_blocked=yes. Try 'use' before turning away.",
プレイ中の動画で見えるJevの判断ログ
画面下の QUAD SYSTEM オーバーレイに、Jev の判断理由がリアルタイム表示されます。
- 判断ごとに
Reason:が更新される - 敵の検出(
enemy_visible,enemy_side) - 被弾方向(
damage_side) - スタック検出(
stuck_timeout)
オーバーレイは Python + Tkinter で別プロセスとして起動し、/tmp/jev_status.txt 経由でゲーム本体と通信しています。
現状の課題
| 項目 | 値 |
|---|---|
| 30 秒での撃破数 | 4 体 |
| 最終 HP | 36 / 100 |
| MAP01 の敵総数 | 18 体 |
| 全滅達成 | 未達(探索時間が足りない) |
stuck_timeout=yes 発生率 |
約 33% |
| 画面外の敵からの被弾率 | 65% |
主な問題
-
探索時間の不足
MAP01 の EXIT 到達には最低 2〜3 分必要。現在のエピソードタイムアウトでは短すぎる
ViZDoom では1回のプレイを「エピソード」と呼び、制限時間(tic 単位)を設定できます。
1 tic = 1/35 秒(DOOM の標準)。frame_skip=8なので、1判断あたり 8 tic 進みます。
-
スタック頻発
同じ場所に 80tic 以上留まるケースが 3 回に 1 回 -
画面外被弾
被弾の 65% は、Jev が敵を視認していない状態で発生
今後の予定
-
criteria の自動探索
Gemini が提案 → Claude Code が実装 → 5 時間走行で評価 -
判断ログのデータセット化
(状態テキスト, Jev の選択)のペアを蓄積 -
長時間無人運転
5 時間で約 7〜8 criteria を評価
まとめ
- クラウド LLM(Jev)で古典 FPS をリアルタイム操作
- 状態も行動指針も自然言語で記述
- 判断理由を可視化することで、研究・デバッグが容易
- 家庭用 PC で動作、特別な GPU は不要
※TypeSafe AI の公式デモで既に「Jev が DOOM をプレイする」は示されています。
本記事の独自性は、criteria による行動制御、判断の可視化、自動探索ループの3点です。
参考
環境: Ubuntu 24.04 / GTX 970 / Python 3.10.21 / ViZDoom 1.3.1
