完成版の配布ページはこちら
アーカイブ
Alpha版公開~最終Alpha版の半分くらいまで
前回は、リリースまでの翻訳作業の雰囲気と、翻訳のルール決めなどについて書いた。
今回の「その5」では、文字通り、「その後」じゃないけれど、実際に翻訳を一段落させて、Alpha版としてリリースした“後”の話となる。
リリースから最近の更新までの歩み
2026年3月4日に、Alpha版として実際に遊べるものを公開した。
成果物はOpenMWの日本語フォーク版(v50)と、日本語化.espなど。
このときはリリースするので手一杯で、肝心の「ちゃんとクリアできるか?」までをチェックしている余裕がなかった。とりあえず日本語版としてのMorrowindをたくさんの人に遊んでもらいたかったし、私自身もずっとMorrowindの翻訳や開発に缶詰状態で、ほとんどやりたいこと(主にARC Raidersとか…)ができていなかったので、とりあえず「動く」感じなのを確認して公開した。
ただ、実際は最初のメインクエストで詰む仕様になっていた…。
公開してからアールさん(Morrowind日本語版でたいへん長らく遊んで下さっている方)からコメントで指摘をもらうまで気付かず、私も初見プレイなのも相まって修正までにかなり時間がかかってしまった。
そもそも、ゲーム自体が古すぎるので、当初はそこまで遊んでいただけると思っておらず…。
この辺の対応の遅さは申し訳ない限りです。
やりたくない病
3月末になってから、ようやくこのAddTopicまわりの不具合とガチンコでやり合うことになった。とにかく進行不可になるクエストが多すぎて、そもそもどうやってそれを調べるか?というところから初めた。
実のところ、OpenMWのソースコードをじっくり読んで、C++をじっくりやり始めたのは、この後の4月初めくらいから…もっと早くやっていれば、ねぇ・・・?
それまでは、.espを解析した情報をもとにした辞書と、自前のビルドツールという貧弱な武器しかなく、やりたくないのでソースはたいして読んでいなかった。
AddTopicとの戦い
少しずつコメントでプレイしてくれた方々からのフィードバックが増え、それに伴って、とにかくトピックまわりの不具合の多さに面を食らった。
そしてこの頃くらいに、トピック周りの仕様を調べて、esmから使える情報を抜き出し、修正しまくる作業が始まった。
この時期は、ずっとこの繰り返し(不具合を見つけては、条件を整理して似たような箇所をまとめて直す)をしていた。
ただ、それでも量が多すぎたので、これらをどうにか機械的に全部抽出し、AddTopicを入れることで進行不可にならないようにする方法を模索し始めた。
機械的な分類とAddTopicの自動注入
報告にあがっていた事例をもとに、ある程度のパターンがわかってきた。
- まず、バニラ(英語版Morrowind)に元々存在するAddTopicがある
- これはまぁ、このままでいいじゃん?
- 次に、テキストマッチングで露見するトピックがある
- つまりトピック語が、「会話に出る」パターン。
- 「私は帝国軍の衛兵だ」 というセリフなら、 →帝国 とか。
- これはDIAL語と完全一致の必要がある。
- 最後に、それ以外
- つまり、AddTopicもされず、露見もしないやつ。
- DIAL語が「アルドルーン」なら、「アルドルン」とかはもうダメ。
この、「それ以外」が初期の日本語版は多すぎた。
AI翻訳による大量の表記揺れと、ダイアログが多すぎるせいで、総チェックとか絶対無理だし、やりたくなかった。
なので、やっぱり(自分がわかる範疇での)Pythonによる機械判定と条件式でマッチさせていくしかなかったが、試行錯誤を重ねた結果、下記のパターンで絞り込むやり方に落ち着いた。
| ティア | 説明 | 件数 |
|---|---|---|
| AUTO_INJECT | 高信頼。自動注入して安全 | 32件 |
| REVIEW_PROMOTE | 中高信頼。レビュー済みで安全 | 36件 |
| REVIEW | 中信頼。レビュー済み | 42件 |
| TEXT_MARKER_ONLY | 低信頼。テキスト言及のみで検出 | 6,551件 |
Bombs away!
上位ティア3つは確定で入れるとして、TEXT_MARKER_ONLYは、そのまま突っ込むと、もはや無差別爆撃だ。
当初は、TEXT_MARKER_ONLYから、さらに細かくティア分けして、少しずつ安全と判断されるものをAddTopicとして追加する、というやり方を検討していたが、私はこれをとりあえずビルドに混ぜた。
なぜそうしたかというと、当時は下記のような状況であった。
- 英語ではテキストマッチングだけで成立しているトピックが大量にある
- 日本語ではそれが壊れている
- 上位ティアだけでは取りこぼしが多すぎる
- かといって全部切ると進行不能が怖い
- だから低信頼でも、とりあえず混ぜる方向にしたのである。
私には、完全なるテキストマッチング(DIAL語完全一致)あるいは、AddTopicしか選択肢になかった。
前者は、まともにやると1ヶ月はかかる。後者は、とりあえず全てのトピックの進行不可は潰せることが明白だった。
リスクを承知で、進行不能回避を優先した結果の実装であった。
AddTopic爆撃は成功?
実のところ、この暫定でのAddTopic爆撃は6,551件と巨大かつ危険であったが、そこまで深く大きなバグを起こしていたわけでもなかった。
というのも、
- 表示されても見た目の違和感で済む(ただ選択肢が多いだけ)
- 任意会話や汎用会話メインで、ほとんどは進行に直結しない
- 条件が重く、実プレイでは表に出にくい
- 同一クエスト内では補完として普通に役立つ
というケースがテストプレイでも多かったし、実際に、
- 報告された明確な破壊的事例は 3件
- 7,964件の「消えたリンク」全体を見ても、真に到達不能だったのは 12トピック / 20行だけ
なので、実態としては
危険候補は大量にあったが、実際に致命傷になる場面は少数
であった。ただ、初対面からラスダン関連トピックが出るなど、露呈は少ない代わりに高威力のバグを残していたので、結果的には失敗であった。
AddTopicは危険なことが判明
あとからソースコードを読んでわかったことだが、テキストマッチングが会話スコープに閉じるのに対してAddTopicはグローバルに作用してしまう仕様になっていた。
テキストマッチングとAddTopicは、外から見ると同じ「トピックを追加する」動作に見えるが、エンジン内部のスコーピングがまったく異なっている。
これは非常に困る!
テキストマッチングの実際の挙動
NPCの応答テキストが画面に表示されるとき、エンジンは addTopicsFromText() を呼ぶ。この関数は2段階のフィルタを持っている。
第1段階: テキスト走査(parseTopicIdsFromText)
応答テキストを KeywordSearch で走査し、既知のDIALトピック名に一致する部分文字列を検出する。
やあ、よそ者。何か用か?ならよそ者がヒットする。
第2段階: NPC応答フィルタ(mActorKnownTopics)
ここが重要な点だ。検出されたトピックは、そのまま追加されるわけではない。
エンジンはまず updateActorKnownTopics() を呼んで、今話しているNPCがそのトピックに対して有効な応答(INFO)を持っているかを判定する。この判定はNPCの種族・クラス・所属・好感度・スクリプト条件をすべて評価する。
応答を持つトピックだけが mActorKnownTopics に入る。
void DialogueManager::addTopicsFromText(const std::string& text)
{
updateActorKnownTopics(); // ← 今のNPCの応答可能トピックを構築
for (const auto& topicId : parseTopicIdsFromText(text))
{
if (mActorKnownTopics.count(topicId)) // ← このNPCが応答を持つ場合のみ
mKnownTopics.insert(topicId);
}
}二重にスコープされてる!
つまり、テキストマッチングは、単に文章中の単語を拾ってトピックを開放しているわけではなかった。
実際には、
- 応答文の中にトピック名があるか
に加えて、
- 今話しているNPCがそのトピックに対して実際に返答できるか
まで見ていた。
だから、文章中にたまたま よそ者 みたいなトピック名が含まれていても、そのNPCがその話題に答えられないなら、プレイヤーの既知トピックには追加されない。
これはめっちゃ重要。早く教えてよ。
テキストマッチングは、文章スコープだけでなく、会話中のNPCスコープにも閉じていた。つまり、意図しないトピック開放をかなり強く防ぐ仕組みになっていた。
AddTopic(スクリプトコマンド)の実際の挙動
一方、ResultScriptの AddTopic はこうなっている。
void DialogueManager::addTopic(const ESM::RefId& topic)
{
mKnownTopics.insert(topic); // ← 無条件で追加!
}つまり、フィルタが存在しない!
トピックIDを受け取って、プレイヤーの既知トピックリストに直接挿入する。今誰と話しているか、そのNPCがそのトピックに応答を持つか、一切関係ない。
一度追加されたトピックは、以降そのトピックへの応答を持つ全NPCとの会話で選択肢として出現する。
なんつーバギーな仕様だ!
こんなものを大量に突っ込んでいたというのか!
まったりライトちゃんには、代わりに怒っておきます。
この差が何を意味するか?
テキストマッチングの場合:
NPC Aの応答に
よそ者が含まれている → NPC Aがよそ者に応答を持つ
→ トピック追加 → NPC Aとの会話で選択可能
AddTopicの場合:
スクリプトが
AddTopic "よそ者"を実行 → 無条件でトピック追加
→ NPC A, B, C, D…よそ者への応答を持つ全NPCとの会話で即座に選択可能
6,551件のAddTopic注入が意味していたのは、383トピックをこの「NPC応答フィルタなし」のルートで追加することだった。
結果として、プレイヤーがまだ会ったことのないNPCや、ストーリー上まだ到達していないクエストのトピックが、会ったばかりの会話に片っ端から突っ込まれ、クエストラインの崩壊を招いていた。
Construction Setのドキュメントからはこの非対称性は読み取れなかった。
なので、アルマレクシアとの初対面でラストダンジョンの選択肢が出る。ディヴァイス・ファーとの初対面で未受注クエストが進行する…あらら…。
というわけで、私は 「どうやって安全にAddTopicを注入するか」ではなく、「AddTopicを使わずにリンクを復元できないか」を最初に考えるべきだった。
クエスト境界フィルタの実装
AddTopicで補正すればクエスト境界が崩壊し、補正しなければリンク消失で進行不能になる。どちらを選んでも壊れる以上、まずは応急処置としてクエスト境界フィルタを実装した。
「同じクエスト内の注入だけ許可すればいいのでは?」という発想で、ESMのジャーナル条件(SCVRサブレコード)を解析し、各INFOがどのクエストの文脈で表示されるかを機械的に判定するフィルタを実装した。
判定は4分岐。
- トピックにクエスト情報がない → マージ(汎用トピックなので安全)
- INFOにクエスト情報がない → ブロック(汎用会話にクエスト固有トピックを入れるのは危険)
- クエストIDに交差がある → マージ(同一クエスト内の正当な補完)
- クエストIDに交差がない → ブロック(クロスクエスト注入)
ブロックされた2,528件の「INFOにクエスト情報がない」ケースの中身を調べると、大部分は Hello、Greeting 5、Idle といったいつでも表示される汎用会話だった。
ここにクエスト固有のトピックを注入すれば、初対面から何でも出る状態になる。ブロックして正解だった。
これによって、6,551件中 842件を安全にマージ、5,709件のクロスクエスト注入をブロック。報告された3件のバグはすべて解消した。
しかし、これはAddTopicの被害を限定する応急処置であり、消えたリンク7,964件の根本対応は別途必要だった。
7,964件の仕分け
クエスト境界フィルタと並行して、「消えたハイパーリンク」の全体像を把握する作業も行った。
7,964件とは、31,504行の全INFOレコードを走査して検出した「英語テキストにはDIALトピック名が含まれているが、日本語テキストには対応する日本語DIALトピック名が含まれていない」箇所の数だ。
- つまり、英語版ではリンクになるが日本語版ではリンクにならない箇所が7,964ある。
しかし7,964件すべてが深刻なわけではない。「消えたリンク」が本当にゲームに影響するのは、そのテキストマッチングがトピック発見の唯一の手段だった場合に限られる。3つの観点で分類した。
| 分類 | トピック数 | 行数 | 深刻度 |
|---|---|---|---|
| AddTopicで到達可能 | 324 | 6,817 | 低(見た目のみ) |
| 他JPテキストに出現 | 472 | (重複あり) | 低(見た目のみ) |
| 真に到達不能 | 12 | 20 | 高(ゲーム影響) |
7,964件が12件に収束した。
残りの大半は Imperial(973行)、need(812行)、Talk(567行)のような英語の極めて一般的な単語だ。
NPCが“the Imperial Legion” と言えば
Imperialがリンクになるし、“I need your help” と言えばneedがリンクになる。
しかしこれらは英語版でも意図的なトピック導線ではなく偶然の一致だ。日本語で消えてもゲームに影響はない。
この種の一般語80トピックのストップリストを作成し、7,964件のうち6,211件(78%)を自動ignoreに分類して、レビュー対象を1,753件に削減した。
いやいや、それでも多いって!
12件の修正
真に到達不能な12トピックは、2種類の方法で修正した。
DIAL訳語の修正(2件)
トピック名自体を、INFOテキスト中に自然に出現する形に変更した。
my tale(私の伝説 → 私の物語): INFOテキストに「私の物語」が9件出現し、テキストマッチングが回復can be reached(到達可能 → 行き方): INFOテキストに「行き方」が5件出現し、テキストマッチングが回復
これは、いつもどおりの修正。
AddTopic追加(10トピック・15行)
残りはDIAL名を変えても自然にテキスト出現が見込めなかったため、特定のINFOに手動でAddTopicを追加した。例えば:
身分について(Background): ゲーム開始直後、税務官のSellus Graviusの会話に追加ちょっとした提案(little suggestion): Crassius Curioのフラール家ホルテイター推薦場面に追加
などなど。 まぁこの辺は適当に。
4/9以降: AddTopicの限界に気づく
クエスト境界フィルタで3件のバグは解消し、7,964件の仕分けで本質的に問題なのは12件だけだと判明した。しかし、構造的な問題は残っていた。
- フィルタの判定が複雑になりすぎる
- 「クエスト情報なし」のINFOの扱いが本質的に曖昧
- AddTopic自体の「無条件追加」という性質は何も変わっていない
先に実装した842件の「安全な注入」も、本当に安全かどうかは個別確認が必要で、そのためには何百時間もかかる。
やったこと①─AddTopicの全面カット
まずAddTopicの影響範囲を確認するために、注入していたAddTopicを一括削除してビルドしてみた。極端なケースを先に試して、どのトピックが本当にAddTopicに依存しているかを特定するためだ。
結果は予想通り、テキストマッチングだけでは到達できないトピックが複数あり、ゲーム進行に影響が出た。ただしこのテストで、AddTopicが本当に必要なのはごく一部で、大半はテキストマッチングか他の経路で到達可能だという感触が得られた。
全面カットは当然そのまま採用できないが、「全部必要」ではないことが確認できた。
やったこと②─ティア分けによる段階管理
AddTopicの信頼度を4段階に分類して管理する仕組みを、さらに精密にする方向も検討したが・・・
なんか色々やってみたけど、結局6,551件の低信頼候補をどう扱うかという問題は残ったままになった。
うーん、難しい。
- 上位3ティアの計110件は条件が厳格で安心して自動注入できる。
- しかし残りの6,551件は言及はあるが安全かどうかわからない。
AddTopicというツール自体にスコープの概念がない以上、どれだけ分類を精密にしても安全にはならない。
つまり、別のアプローチが必要だった。
転機: マーカーという仕組みに気づく
@テキスト# 構文の発見
そんなこんなで、AddTopicが危険なのをやっと理解したので、AddTopicに頼らず、別の方法でテキストマッチングを復元できないかを探ることにした。 従来のようにスクリプトに頼ると危険なので、ゲーム内仕様ではなくエンジン側の改修でなにかできないかを模索する方針へ転換した。
とりあえず、OpenMWのソースコードを読み、会話トピックの判定がどのように処理されているのかを確認していくことにした。
まず keywordsearch.cpp と dialogue.cpp を追いかけた。テキストマッチングの仕組み自体はシンプルだった。Trieツリーに登録されたDIALトピック名を応答テキスト中から検索し、一致部分をハイパーリンクにする。
そしてソースを読み進めていく中で、@テキスト# というマーカー構文の存在に気づいた。テキスト内に @coded message# と書けば、KeywordSearchのTrieツリーとは独立に、明示的なハイパーリンクを生成できる。
これはまさに欲しかったものだ!!
テキストの翻訳を変えなくても、好きなところにリンクを埋め込める。AddTopicのようなグローバルでトピックを追加しまくる副作用もないし、 INFOレコードのテキストにチョロッと書くので、スコープは自動的にそのNPC・その会話で完結されられる。
しかし試してみると、いくつか難点があった。
難点①─表示テキストとトピック名が完全一致しないと使えない
@暗号化されたメッセージ# と書かないとリンクにならない。例えば、文章中では「伝言」としか書かれていない場合、@伝言# と書いてもトピック暗号化されたメッセージにはマッチしない。
これじゃ意味ない。
難点②─グローバル辞書(.top)は同音異義語で破綻する
OpenMWには .top ファイルという仕組みがある。「伝言→暗号化されたメッセージ」のようなマッピングを登録しておけば、@伝言# で正しいトピックに飛べる。
しかしこれはグローバル辞書だ。「依頼」という語が、あるクエストでは「仕事」を指し、別のクエストでは「命令」を指すケースがMorrowindには多い。
日本語の特性上、グローバルに1対1で決めると使いづらい。
難点③─暗黙リンクとの排他 ― hasTranslation() の発見
ソースをさらに読み込んでいて、決定的な発見があった。
OpenMWの dialogue.cpp(表示レイヤー)には、翻訳MODが有効かどうかで処理を分岐するコードがあった。
// dialogue.cpp (Response::write) — 2025.12.24本家
if (hyperLinks.size()
&& windowManager->getTranslationDataStorage().hasTranslation())
{
// 明示リンク(@...#)だけを表示。highlightKeywords は呼ばない
}
else
{
// 通常パス: highlightKeywords でテキスト中のキーワードを自動リンク化
keywordSearch.highlightKeywords(text.begin(), text.end(), matches);
}同様の分岐が journalviewmodel.cpp(ジャーナル表示)にもあった。
こいつは、やっかいな仕様だ。
少し整理しよう。
- 本来なら、NPCの応答文の中にトピック名が含まれていれば、エンジンが自動でそれを検出し、ハイパーリンクにしてくれる。これがMorrowind本来のテキストマッチングである。
- しかし
.celや.topなどの翻訳データが読み込まれていると、OpenMWは「翻訳環境では自動ハイパーリンクを使わない」と判断する。その場合、表示されるリンクは@テキスト#で明示されたものだけになる。 - 日本語MODでは当然
trueになる。- なので、「本文中の単語を自動で拾ってリンクにする処理」が使われなくなる。
- ハイパーリンクが止まると、単に見た目のリンクが減るだけではなく、会話の進行そのものが切れる。
つまり、OpenMWの開発者は「翻訳時はキーワードマッチが壊れる」とすでに認識していたのだ。
OpenMWの他言語翻訳の想定
英語なら、本文中の 暗号化されたメッセージ などのDIAL語をそのまま探せばいい。しかし翻訳後の本文では、その文字列は日本語に置き換わっている。英語のトピック名を本文から探しても、当然ヒットしない。
そのためOpenMWの表示レイヤーでは、翻訳MODが有効な場合、本文中の単語を自動で拾ってリンクにする処理を使わない設計になっていた。 代わりに、@...# で明示されたリンクだけを信用する。
つまり、開発者は「翻訳された文章ではキーワード自動検出は当てにならないから、リンクしたい箇所は @...# で明示してくれ」と言っているのである。
ところが、表示側はすでに「翻訳時は @# マーカーを使う」という方針になっているのに、ゲームロジック側のトピック発見処理は、まだ従来のキーワード検索に頼っていた。
// dialoguemanagerimp.cpp — ゲームロジック (トピック発見)
void DialogueManager::addTopicsFromText(const std::string& text)
{
// HyperTextParser::parseHyperText は明示リンクも暗黙キーワードも
// 両方トークン化する。hasTranslation() のチェックはない。
for (const auto& topicId : parseTopicIdsFromText(text))
{
if (mActorKnownTopics.count(topicId))
mKnownTopics.insert(topicId);
}
}
addTopicsFromText()は、翻訳MODが有効かどうかを見ていない- 表示側では暗黙リンクを止めているのに、裏側のトピック発見では、
KeywordSearchによる本文検索がそのまま動いている
つまり、表示とゲームロジックで方針がズレているのだ。
- 画面上では、翻訳環境なので自動リンクは表示されない。
- しかし裏側では、従来どおり本文を走査してトピックを探そうとしている。
この状態では、プレイヤーから見て挙動が分かりにくい。画面にはリンクが見えないのに、裏ではトピックが追加される場合がある。逆に、英語のトピック名が日本語文中に存在しないため、本来開くべきトピックが発見されない場合もある。
この発見で、マーカーが単なる代替手段ではなく、OpenMWの翻訳アーキテクチャが最初から意図していた正統な仕組みだとわかった。
OpenMWの表示側は、すでに @# マーカーを翻訳環境の正規ルートとして扱っている。
ならば、ゲームロジック側のトピック発見も、それに合わせるべきじゃん?
そう考えると、AddTopicで無理やりトピックを開放するのではなく、表示文に埋め込まれた @# マーカーをゲームロジック側でも読ませるのが自然だ。
問題は、既存のマーカー構文では表示テキストとトピック名が一致する必要があり、日本語翻訳では使い物にならないこと(難点その①, その②)であった。
そこで @表示テキスト=トピック名# の拡張構文が必要になった。
この形なら、翻訳文の自然さを保ったまま、どの英語DIALトピックに接続するかを明示できる。
表示側とゲームロジック側の両方がこのマーカーを読むようにすれば、AddTopicのようにグローバルな副作用を起こさず、会話本文に書かれた範囲だけで安全にトピックを開放できる。
@表示テキスト=トピック名# 構文の開発
OpenMWソースの改造
3つの問題を一気に解決するために、OpenMWのソースコードを直接改造することにした。
また、このタイミングで先にOpenMWの最新版(v51)を取り込むことにした。本家にもトピックまわりで改修が入っていたことに気づいたからである。
1. @表示=トピック# 拡張構文の追加
@伝言=暗号化されたメッセージ#
= の左が画面に表示されるテキスト、右が実際のトピック名。これで、
- テキストの翻訳を変えなくてよい(伝言のまま)
- 正しいトピックに紐づく(暗号化されたメッセージ)
- INFOレコードごとに個別指定できる(グローバル辞書の問題なし)
同じ「依頼」という単語でも、Aの会話では @依頼=仕事#、Bの会話では @依頼=命令# と書ける。
2. 暗黙リンクとの共存(hasTranslation() 排他の解消)
- 2025.12.24版では
hasTranslation()=trueのとき表示レイヤーが暗黙リンクを完全にスキップしていた - 2026.03.28版のリファクタリングで
KeywordSearch::parseHyperTextに統合された際にこの排他は解消され、明示リンクと暗黙リンクが自然に共存するようになった。
@伝言=暗号化されたメッセージ# でマーカーを入れても、同じテキスト内の他のキーワードマッチは従来通り機能するように実装した。
3. dialoguemanagerimp.cpp の統合
addTopicsFromText が @# マーカーも KeywordSearch も両方処理するように実装した。
なので、マーカーを入れれば AddTopic スクリプトは不要になる。
改造範囲を最小にする工夫
当初は dialogue.cpp、journalviewmodel.cpp、dialoguemanagerimp.cpp、keywordsearch.cpp の4ファイルすべてを大きく変える方針だった。
しかしソースを比較していくうちに、keywordsearch.cpp だけで核心部分が成立することに気づいた。
2026年3月版のOpenMW本家は、すでに parseHyperText() と getDisplayName() を使う構造になっていて、keywordsearch.cpp に = の右辺を取る処理を足すだけで、下流の dialogue.cpp や journalviewmodel.cpp は自動的に機能するようになっていた。
これは「安全圏から着手する」という方針にも合致していた。
マーカーがAddTopicより本質的に安全な理由
ここが最も重要なポイントだ。
AddTopicは「このトピックを知れ」という命令。
NPCが応答を持つかどうかに関係なく、無条件にトピックが追加される。だからクエスト境界を簡単に破壊する。
マーカーは「このテキストはリンクである」という宣言。
ゲームエンジンは、リンク先のトピックについてそのNPCが応答を持っている場合にのみ、トピックを追加する。応答がなければ何も起きない。
これは英語版でのテキストマッチングとまったく同じ動作だ。マーカーは「翻訳で壊れたテキストマッチングを、別の手段で復元する」もの。AddTopicのような「英語版にない挙動を新たに追加する」ものではない。
726行の手動マーカー化
翻訳用の開発ツールを強化
実は、OpenMW日本語版の開発初期段階で、日本語化用のツール(BG3のMOD翻訳ツールを改修したもの)を作っていた。開発用なので私にしか使い方が分からないグチャグチャのものだが、これを大幅に改修して、今後の保守がしやすいように設計変更を進めていた。
マーカー構文がエンジン側で動くことが確認できたので、このマーカー処理を簡易的にできるように、日本語化ツールも同時に改修した。
マーカーにしたいところをドラッグすれば、マーカー構文化できるようにしたり、機械判定でマーカー対応されてない部分を検知して編集候補に出したり…などの機能を実装した。

ひと通り土台ができたら、まずは「このNPCは対象トピックへの応答を持っている(speaker一致あり)」のINFO行を抽出できるようにして、精査したところ、約750行が候補として出てきた。
各行について、
- テキスト中にトピック名の部分一致があるか確認
- あれば、その部分を
@表示テキスト=トピック名#に変換 - 変換結果が文脈として自然か目視確認
この作業を726行分完了した。
AddTopic依存の大幅縮小
マーカー化の結果、従来のAddTopic(約5,000件超)の大部分が不要になった。
- 英語版ESMにもともとあるAddTopicはそのまま残す
- 日本語版が独自に注入していた5,000件超のAddTopicは原則廃止
- ジャーナルINFOへの無意味な注入(738件)も削除
現在は英語版ESMにもともとあるAddTopicのみが残っている。クエスト境界を越えるような危険な注入は全てブロック済み。
.mrk と .top の自動生成
マーカー情報をゲームに渡す .mrk ファイルと、旧形式との互換用の .top ファイルを、ESPビルド時に自動的に生成するよう統合した。手で管理する必要がなくなり、ESPを再ビルドすれば常に最新のマーカーデータが揃う。
.topは日本語化環境では使用しないが、将来用として一緒にビルドされるようにした。
CJK字幕折り返しの一時消失
マーカー実装と並行して、もう一つトラブルがあった。ビルド環境の更新中に、日本語の字幕が折り返されなくなった。
OpenMWが使っているUIライブラリ(MyGUI)は、元々スペース区切りでしか改行しない。日本語のようにスペースなしで文が続く言語では、文末まで突き抜けてしまう。
12月のJPフォークではCJK文字の境界で折り返すパッチを当てていたのだが、2026年3月版の本家ベースに乗り換えた際にパッチ適用済みバイナリが未適用版に戻ってしまった。
原因特定後、ビルドシステムに apply_mygui_patch.cmake というパッチ自動適用の仕組みを組み込み、ソースからMyGUIを毎回ビルドする構成に変更した。これで今後同じ問題は起きない。
AddTopic問題に終止符
従来5,000件超あった独自AddTopicは全廃し、726行のマーカーに置き換えた。マーカーはテキストマッチングと同じスコープで動作するため、英語版と同等のクエスト境界が回復した。
もちろん、抽出フィルタにミスがあってマーカーの入れ忘れが残っている可能性はある。しかし、仮に漏れがあっても影響はその1行に閉じる。AddTopic時代の「1件の注入ミスがゲーム全体のクエスト境界を崩壊させる」リスクとは質が違う。
万が一残っていたら、報告お願い致します。
アーカイブ
コメント