ゲーム紹介

実機画面です。この乱戦はスクリプトで配置し発砲のタイミングも制御していますが、船、砲弾、爆発、砲台の砲撃はすべてゲーム本来の表現です。
POLYSEA は 1650 年のカリブ海を舞台にした航海ストラテジーゲームです。イングランドとスペインが争い、海賊が略奪を繰り返し、スケルトン艦隊が夜の海をさまよう世界で、海軍に仕えることも、黒旗を掲げて海賊になることもできます。艦隊を率いて舷側砲を撃ち合い、鉤縄で敵船に乗り込み、士官を集めて船室の持ち場に配置し、港を建設して交易路を開き、群島と沈没船の財宝を探します。
| 項目 | 内容 |
|---|---|
| ジャンル | 航海ストラテジー |
| プレイ | シングルプレイ / Steam マルチプレイ |
| 対応環境 | Windows |
| 言語 | 中国語(多言語版を計画中) |
| 選べる勢力 | イングランド、スペイン、4 つの海賊団 |
| エンジン | Unity 6 |
| 開発 | TechNeko |
| 状態 | 開発中、テスト版 v0.8.50 |
ゲームの特徴
海戦
砲は 1 門ずつ個別に装填・発砲します。砲は 6 種類、砲弾は 4 種類あり、風向き、射界、天候、視界が戦局を左右します。見下ろしの海図視点で艦隊全体を指揮することも、船長視点に切り替えて自分で舵を取ることもできます。
接舷
鉤縄で引き寄せて渡し板を架けると、戦いは甲板上のリアルタイム戦闘に移ります。接舷戦術は 29 種類。水夫 1 人ひとりが戦場の 1 人として刃を交えます。

船員
船長と士官にはそれぞれ能力とスキルがあり、適した船室に配置して武器や宝物を持たせます。士官は持ち場の仕事と手柄で経験を積みます。
港と交易路
港の建物は 6 種類。町はテントから宮殿と要塞へ育ちます。商船隊は航路に沿って荷を運んで利益を生み、港務庁では総督と守備隊を任命します。

世界
4 つの勢力、フォグ・オブ・ウォー、天候と昼夜、島の探索と宝探し、網漁。未探索の海は手描きの羊皮紙の海図で、どの島も実際の姿のまま描かれます。

マルチプレイ
Steam で友人とつながり、同じ海で肩を並べて戦えます。
開発の振り返り
ここからはこのゲームの開発記録です。技術的な手法と問題解決の過程を中心に書いています。
プロジェクトは 2026 年 9 月 23 日の未明に始まりました。最初のコミットが 04:05、04:58 には船を動かし、舷側砲を撃ち合い、接舷と入港ができるプロトタイプがありました。その後の 1 か月、C# コードとシェーダーは Claude Code が書き、人は方向決め、テストプレイでの検収、アート素材の選定、音のチェックを担当しました。
本記事の日付はプロジェクトの開始日で、内容は 2026 年 10 月 24 日の v0.8.50 までを扱います。
| 項目 | 値 |
|---|---|
| 開発期間 | 2026-09-23〜2026-10-24、計 32 日(うち 30 日にコミットあり) |
| git コミット | 663 回 |
| ゲームコード | C# 259 ファイル、87,031 行 |
| エディターツール | C# 32 ファイル、9,748 行 |
| 自作シェーダー | 12 個 |
| サウンド | 録音ファイル 410 個、191 スロット |
| ネットワークプロトコル | 第 24 版 |
結論から
- 変更のたびにベースラインを持つ。 海面は 23 時間足らずで 36 コミット分変更し、最後は丸ごと巻き戻しました。以後のルールは、一度に 1 層だけ変更し、各ステップで前後比較を示すことです。
- エージェントには見えないし聞こえない。検証手段が品質を決める。 映像はスクリーンショットとフレーム単位の比較、音はラウドネス測定、戦闘は自動演習で確かめます。測れない問題は直せません。
- 定量化できるものは定量化する。ただしサンプル数を見る。 接舷戦のバランス調整では 100 戦以上のシミュレーションを回しましたが、そのうち 16 戦から出した結論は、乱数シードを変えると逆転しました。
- エディターで通っても、通ったことにはならない。 一部のレンダリング問題はビルド後にしか出ないため、ビルド版の自己テストを専用に追加しました。
- 高価なエフェクトは必要な場所に置く。 リアルな波は見下ろしカメラで 2 度失敗し、低い位置の船長視点だけに入れて初めて成立しました。
構成
| 項目 | 内容 |
|---|---|
| エージェント | Claude Code(デスクトップ版)、プロジェクト全体を同一セッションで連続作業 |
| モデル | Claude Opus 5.5 が中心、一部の時間帯は Claude Sonnet 5.5 |
| エンジン | Unity 6000.3.24f1、URP 17.3(Forward+) |
| マルチプレイ | Netcode for GameObjects 2.13.3、Steamworks.NET、自作の Steam トランスポート層 |
| 水 | Stylized Water 3 + 自作シェーダー |
| アート | Synty POLYGON シリーズのモデル、Classic RPG GUI の UI 素材、Human Mega アニメーションパック |
| 人の役割 | 方向決め、テストプレイでの検収、素材選び、音の試聴と評価、最終判断 |
UI はすべてコードで組んでおり(uGUI)、UI ディレクトリは 19,076 行と、ワールドロジックの World(23,328 行)に次ぐ 2 番目に大きなモジュールです。エージェントにとってはエディターでコントロールをドラッグするよりコードを変更するほうが確実で、変更も git で 1 行ずつ確認できます。
タイムライン
| 日付 | マイルストーン |
|---|---|
| 09-23 | 最初のコミット、53 分後に遊べるプロトタイプ。同日に近景の接舷戦と上陸戦を追加 |
| 09-25 | 陣営、シード生成マップ、クエスト。海面を Stylized Water 3 に変更 |
| 09-26 | 友人に配った v0.2(73 MB)。効果音を素材パックに変更 |
| 10-01 | リアルな波を試し、コミットの 13 分後に巻き戻し |
| 10-02 | マルチプレイの実現可能性を監査:44,084 行のコード、主な障害 8 つ |
| 10-03 | マルチプレイ改修の 6 段階と Steam トランスポート層、この日 53 コミット |
| 10-05 | 音響要件ドキュメント:効果音 185 個、楽曲 13 曲 |
| 10-09〜10-10 | 録音の組み込み 4 ラウンド、音楽と環境音のシステム |
| 10-12〜10-13 | KWS の写実的な水と嵐の海面、36 コミットの後に全面巻き戻し |
| 10-15〜10-17 | 接舷戦をリアルタイム戦術に変更 |
| 10-19〜10-20 | 羊皮紙のフォグ・オブ・ウォー。戦闘では 1 人をキャラクター 1 体で表示 |
| 10-21 | マルチプレイでの共有アカウントの競合を解決 |
| 10-22〜10-23 | 船長視点、うねり、積雲の天候、雨霧 |
| 10-24 | v0.8.50:海賊の賞金任務と酒場の情報 |
海面:4 回作り、振り出しに戻ってから先へ進む
水はこのプロジェクトで最も作り直しの多かった部分です。ゲームの既定カメラは海面の約 340 m 上にあり、この前提がその後のほぼすべての判断を決めました。ただ、それを本当に理解したのは試行錯誤を経てからでした。
第 1 版:LOD メッシュを捨て、海は 1 枚の大きな板に
9 月 25 日に Stylized Water 3 を導入した際は、まずプラグイン付属の海洋 LOD メッシュを使いました。各 LOD リングはそれぞれのグリッド間隔でスナップし、隣り合うリングは継ぎ目で重なります。海面は半透明なので、重なった部分の水が 2 回描かれ、浅瀬に横方向の色の帯が現れて、カメラのズームに合わせて動きました。一方で、こちらの海は unlit のトゥーンシェーディングで、メッシュによる起伏はほとんど見えません。
そこで海は 1 枚の大きな四角形に変更し、表現は上に重ねた自作シェーダー SeaStrokes の層に移して、次の要素を順次加えていきました:
- 波紋:2 枚のノーマルマップをマルチスケールでサンプリングして別々の方向へ流し、太陽方向からトゥーン調の明暗の帯と細いハイライトの線を計算する。
- マスク:大スケールのノイズで海の一部を別の部分より粗く見せ、タイリング感を隠す。
- きらめき:太陽に向いた波面に一瞬光る小さな輝点。曇天時、船の影の下、カメラが高すぎるときは消える。
- 影:海自体はライトを受けないので、船、帆、島の影はこの層が水面に描く。
- 深度フェード:デプスマップから水深を読み、浅瀬では層全体をフェードアウトさせて、砂の海底と岸の泡を残す。
2 回目:リアルな波、コミットの 13 分後に巻き戻し
10 月 1 日にリアルな波を試しました。800 m 四方の水面メッシュをカメラに追従させ、CPU 上で同じ Gerstner 波を計算して船を上下させる方式です。近くで見ると悪くありませんでしたが、問題が 3 つありました:
- 短い波が 3〜4 m のグリッドでエイリアシングを起こし、カメラを引くと規則的な模様になる。
- 波自体は目立たないのに、船の揺れだけが速く細かい。
- 水面が下がると、岸の泡の輪からときどき岩礁がのぞく。
プロデューサーの判断は、この種の問題は想像より多く出てくるので、本当に良い方法が見つかるまではやらない、というものでした。コードは巻き戻し、git の履歴にのみ残しています。
3 回目:写実的な水に変更、23 時間後に全面巻き戻し
10 月 12 日に KWS Water System を試用で導入し、同時に TENKOKU Dynamic Sky も試しました。後者はビルトインパイプライン専用で、URP では何も描画されないうえ null 参照例外も投げるため、その場で脱落。KWS の数字は魅力的でした。エディターで 2560×1440、RTX 4070 Ti の条件で測ると、GPU はこちらの海より 0.3〜1.7 ms 増えるだけ、VRAM は 155 MB 増、CPU は変化なし。その晩のうちにメインへマージし、設定に写実とトゥーン調の切り替えを追加しました。

上段はゲーム既定の 340 m カメラ、下段は低い位置の近景。左列は当時のトゥーン調の海、中列は KWS の既定プリセット、右列はカリブ海の配色に調整した KWS。近景では KWS が明らかに優れていますが、ゲームで実際に使う高さではその優位が消えます。
続いて修正の連続です:
- KWS の波は実ジオメトリなので、波頭が海面の射程円を隠します。海面のマーカーをすべて、頂点を視線方向に持ち上げる 1 つのシェーダーに変え、持ち上げ量を現在の最大波高に追従させました。
- 夜は平坦で暗い。月は南にあり、カメラは北を向いているので反射が決して出ず、水の色を暗くして泡を明るくするしかありませんでした。
- 動的な波の航跡は、範囲 260 m、1 m あたり 4 ピクセルの設定では池のさざ波のようで、スケールが明らかに合いません。
- KWS2 から KWS1 に戻すと、エディターが Play に入った途端にクラッシュし、
d3d11: out of range sampler binding pointと報告しました。原因はシェーダーキャッシュの破損で、Library/ShaderCacheを退避すると復旧しました。
並行してトゥーン調の海でも嵐を作っていました:白波、砕波、船首の波しぶき、飛沫。テストプレイ後のプロデューサーの結論は 2 つ。KWS はゲームの高さから見ると元のトゥーン調の海に及ばない。トゥーン調の海のほうも、すでに手を入れすぎて壊れている。
最初の巻き戻しは KWS の前までで、同じ日に加えた白波と泡は残ったままでした。プロデューサーが求めていたのは、KWS より前、海面の作業すべてより前のあの海です。2 度目の巻き戻しで v0.8.8 まで戻って、ようやく決着しました。KWS のマージから全面巻き戻しまで、23 時間足らず、36 コミット。コードはタグに保存し、残したのは 3 つだけです:どのズームでも細かく見える雨、ゆっくりした天候の遷移、速度を落とした雲間の光。
残ったものはもう一つ、ルール集です。どれも却下された案と引き換えに得たものです:
| ルール | 理由 |
|---|---|
| 泡にセル状テクスチャを使わない | 網目模様は一目でテクスチャだとわかる |
| 砕波のタイミングに滑らかな空間位相場を使わない | 発生が波面のように掃いていき、高速で走る白い点に見える。砕波ごとに独立した位置、大きさ、寿命が必要 |
| 動く物体に貼る泡は、ノイズをオブジェクト空間でサンプリングする | 船体座標系のマスクにワールド座標のノイズを組み合わせると、縁が毎フレーム変わって震えて見える |
フローのオフセットは積分で求め、t * speed を使わない | 速度が変わると、後者は画像全体が飛ぶ |
SW3 のノイズテクスチャを読むときは先に pow(x, 1/2.2) をかける | sRGB でインポートされており、シェーダーが読む平均値は 0.5 ではなく 0.24 で、しきい値がすべてずれる |
| 海面の動きは船を追うカメラで測る | プロデューサーが繰り返し指摘した「猛スピードで流れていく層」は、こちらの固定カメラのテストではまったく見えなかった |
最後の 1 つが最も価値のあるものでした。エージェントの目はカメラです。カメラの置き場所を間違えれば、スクリーンショットをいくら撮っても意味がありません。以後の海面の問題は、船を追うカメラで短いクリップを録画し、1/3 秒ごとに 1 フレームを抜き出したフィルムストリップで確認しています。
4 回目:一度に 1 層だけ変更する
v0.8.8 の海からやり直しました。ルールは、一度に 1 層だけ変更し、前後比較を示し、ほかには触らないことです。
最初の作業は、プロデューサーの言う「海底のザラつき」です。層を 1 つずつオフにして、ようやく特定できました。海底を隠しても、25 m 下げてさらに暗くしても映像は変わらないので、海底ではない。SeaStrokes を切っても模様が残るので、水自体のノーマルマップが原因です。このテクスチャは 5 m ごとに繰り返し、2 層目のスケールはちょうど半分で、リズムが極めて規則的でした。これを 12.5 m に変更し、2 層目のスケールを 0.37 にしました。
2 番目は六角形タイリングです。Stylized Water 3 の Normals.hlsl にある SampleNormals を改変しました。平面を六角形に分割し、各セル内のテクスチャを個別に平行移動・回転させ、最も近い 3 セルをブレンドします。これで繰り返しが見えなくなり、継ぎ目も出ません。ストアアセットは git に入れないので、改変したファイルと元のファイルを一緒にリポジトリの ThirdPartyPatches/ に置き、プラグインを再インポートしたら上書きして戻すよう明記しました。
船長視点:ようやく波の居場所ができた
10 月 22 日に World of Sea Battle に似た船長視点を追加しました。カメラを船のすぐ後ろに寄せ、WASD で直接舵を取ります。低いカメラでは平らな海はたちまちボロが出ます。一方、タイトル画面のうねりはプロデューサーがずっと気に入っていたので、この視点でだけリアルな波を有効にしました:
- カメラの周囲に 760 m、頂点間隔 2 m の
WaterGridを置き、カメラに追従させる。 - 遠方は平らな海のまま。あの大きな四角形は、このとき中央に四角い穴の開いたリングに変わる。
- 波高はカメラから 140〜300 m の間でゼロまでフェードアウトするので、グリッドの縁と平らな海はぴったり一致する。
- 船は CPU 上で、プラグインと同じ Gerstner 波形、同じクロック、同じフェードカーブで高さを計算し、イージングをかけて上下する。ゲームルールは引き続き平面の海として計算する。

見下ろしカメラの海にはまったく手を加えていません。10 月 1 日の 3 つの問題にもそれぞれ答えが出ました。この視点はカメラが低く、高く引いてエイリアシングが見えることはない。船の上下はイージングを通すので、もう引きつらない。主島の周辺で岸を確認したところ、岩礁はのぞかなかった。
しかし初めて船長視点を開いたとき、波は早送りのように暴走しました。原因は 2 つのシステムの間に隠れていました。SeaMood は模様が風に流れるよう、海面マテリアルの _Direction を毎フレーム変更します。一方、プラグインは波の位相計算にこの方向とワールド座標の積を使います。原点から数 km 離れると、方向のごくわずかな変化でも波形が滑り、2.5 km 先の船の位置では約 4.5 m/s の速度が余分に加わっていました。方向成分によって波形も 1.8 倍に押し潰されていました。解決策は次のルールです:波を有効にしたマテリアルでは、_Direction を一定に保つ。CPU で高さを計算する際はグローバルの _CustomTime を読む必要もあり、WaterObject.CustomTime は使えません。後者の getter は常に -1 です。
霧にも似たような見えにくい問題がありました。海面にまったく霧がかからず、水平線が刃物で切ったようでした。調べると、プラグインのシェーダーのインポート設定で、霧の統合方式がプロジェクトに入っていない SC Post Effects になっていました。Unity の霧に変えると正しくなりました。このフォルダーも git に入れないので、再インポート後に設定し直すことをメモに残しています。
海面のマーカー
小さいながら典型的な問題がもう一つあります。射界の扇形は、複数の砲が重なっても濃くならないよう、ステンシルバッファで各ピクセルを 1 回だけ塗るようにしています。最初は bit 64 を使いましたが、扇形が海面にまったく表示されませんでした。SW3 の水は全ピクセルにステンシル値 65 を書き込み、bit 1 と bit 64 を占有していたのです。128 に変えると正常になりました。その後、味方と敵の射界が重なると先に描いた側しか表示されないことがわかり、それぞれに 1 ビットを割り当てました:味方 128、敵 32。
音楽と効果音:コードによる合成から測定パイプラインへ
第 1 版:コードで合成
開始から 1 日足らずで、SfxSynth は砲声、爆発、水しぶき、雷鳴、雨のループをコードで合成できるようになり、素材には一切依存しませんでした。問題も明らかでした。エージェントには聞こえず、スペクトルは計算できても、良い音かどうかは判断できません。9 月 26 日に素材パックの効果音へ切り替え、砲の重さで段階分けしました。10 月 2 日にはプレイヤーから波と豪雨がうるさいとの声があり、環境、戦闘、UI、音楽の 4 チャンネルとマスター音量を追加しました。
音を、要件ドキュメントのある職種として扱う
10 月 5 日に音響要件ドキュメントを書きました。効果音 185 個と楽曲 13 曲の各項目に、番号、トリガーのタイミング、長さ、再生方式、バリエーション数、優先度(P0〜P2)、現在の状態、音の要件を記載しています。納品仕様も固定しました:WAV、44.1 または 48 kHz、効果音はモノラル、環境音と音楽はステレオ、ファイル名は <id>_<n>.wav。聞き取れる人の声は含めず、将来の多言語化に備えます。
音はこのプロジェクトの外でドキュメントに沿って制作され、第 1 弾として 424 個の WAV が納品されました。それを人が 1 本ずつ試聴・評価し、結果を JSON に保存します。ここでエージェントは音質を判断せず、評価を通ったファイルだけを組み込み、ドキュメントの該当行を「組み込み済み」に更新します。
| ラウンド | 日付 | 新規合格ファイル | 累計ファイル | 累計スロット |
|---|---|---|---|---|
| 第 1 ラウンド | 10-09 | 256 | 256 | 122 |
| 第 2 ラウンド | 10-10 | 64 | 320 | 152 |
| 第 3 ラウンド | 10-10 | 53 | 373 | 178 |
| 第 4 ラウンド | 10-10 | 36 | 409 | 191 |
その後の音楽更新で 1 曲増え、現在のファイル数は 410 個です。これらの音は CC BY、CC0、パブリックドメインの 519 の音源を翻案したもので、カット、レイヤー重ね、フィルター、速度・ピッチ変更などの処理を経ています。クレジット一覧はスクリプトが素材表から生成してゲームと一緒に配布しており、3,000 行以上あります。人手での維持は現実的ではなく、しかも CC BY のクレジットは必須要件です。
ラウドネス:耳の代わりに BS.1770
エージェントには聞こえませんが、測ることはできます。これがこの節で最も役に立った決定です。Tools/Audio/audio_loudness.py は ITU-R BS.1770 に従って各ファイルを測定します。放送業界で使われる K 特性のラウドネスです:
| 種類 | 測定方法 |
|---|---|
| ワンショットの効果音 | 最も大きい 400 ミリ秒(momentary max)。400 ミリ秒未満のものは 400 ミリ秒として扱う |
| ループと音楽 | 全体の積分ラウドネス(integrated LUFS) |
次に、各番号がゲーム内で持つべき相対ラウドネスを表に書きます。例えば艦砲 -18、重砲 -17、旋回砲 -21、ボタンクリック -32、マウスホバー -40、波 -34、航海音楽 -32。スクリプトはここから各スロットの音量と、同じ番号の各バリエーションのゲインを算出し、RecordedSoundTable.cs を生成します。
表全体は砲声を基準にしています。録音版の砲声を旧版の砲声の位置に合わせるため全体を -4.8 dB オフセットし、まだ置き換えていない旧い音のミックス内の位置が変わらないようにします。測定結果はファイルの sha でキャッシュし、次のラウンドでは新しいファイルだけを測ります。以後の各ラウンドの流れは 5 ステップに固定されました:ファイルのコピー、測定とラウドネス表の生成、インポート、新しい番号へのトリガー追加、ドキュメント更新。
音楽と環境音
Music.cs には 9 種類のムードがあります:タイトル、昼の航海、夜の航海、嵐、海戦、接舷、港、島探索、幽霊船。2 つの AudioSource でクロスフェードし、通常は 2.5 秒、戦闘突入時は 1 秒です。
音楽を煩わしくしないのは、いくつかの小さなルールです。穏やかな曲は 1 回流し終えると 25〜70 秒の無音を挟んで次の曲へ移り、ループするのは緊張感のある曲だけ。戦闘音楽は最後の砲撃からさらに 12 秒続く。港の近くを通るような短い変化では 2 秒待ってから切り替え、行ったり来たりを防ぐ。海戦の終わりには短いフレーズを流し、同時に背景の音を下げる。
Ambience.cs は環境音を担当し、一組のループレイヤーで構成されます:天候で変わる風、夜の海、港の人の声、岸の波、島の虫や鳥、帆の風音。各レイヤーはカメラに映る状況に応じてフェードイン・アウトし、カモメ、霧の中の鐘、突風がときおり鳴ります。通知音にも節度が必要で、例えば敵艦の帆影を発見した通知音は 25 秒に 1 回まで、開始から 5 秒間は鳴らしません。
実装上の小さな落とし穴
forceToMonoがインポート 1 回おきに反転する。インポートコードはチャンネル数で設定を決めますが、モノラルにミックスされたクリップは 1 チャンネルと報告するので、次の判定は当然逆になります。Startという名前で引数を持つメソッドは、Unity がライフサイクル関数として扱い、Start() can not take parametersと報告します。この手の名前は使えません。- プロモーション映像は固定フレームレートで録画する必要があり、ゲームのリアルタイムの音とは合いません。
ClipRecorderはフレームの保存に加え、各 AudioSource の開始、音量変化、停止をフレームごとにsound.csvへ書き出し、スクリプトがそのログから音声トラックを再構築して、フレーム単位で同期させます。
マルチプレイ:1 日でアーキテクチャを作り替える
10 月 2 日のコード監査では 8 つの障害が挙がりました。例えば、GameManager.player と静的な PlayerFaction が 200 か所以上で参照されている。isPlayer は約 150 か所あり、「人間が操作」と「このマシンが操作」の 2 つの意味を兼ねている。ポーズは Time.timeScale 頼み。接舷と上陸はシングルトン。シードを設定していない UnityEngine.Random が 330 か所以上あり、船の運動は PhysX の接触に依存している。最後の 2 点で lockstep は即座に除外され、ホスト権威と状態スナップショットの組み合わせしか道はありません。
技術選定は Netcode for GameObjects 2.x、リッスンサーバー方式で、トランスポート層は SteamNetworkingSockets をベースに自作しました。10 月 3 日はまず読み取り専用のエージェント 6 体に手分けして監査させ、結果を 6 段階の改修計画にまとめてから、段階ごとに完了させました。この日 53 コミットです。
作り終えた後に代償が来ました。代表的な問題は次のとおりです:
- エディターのクラッシュ。 デバッグコールバックの関数ポインタを Steam に渡していたため、スクリプトのリロード後に Steam のスレッドが解放済みメモリを呼び出しました。今はビルド版を特定の引数で起動したときだけ登録し、リロード前に解除します。
- Play 中の再コンパイルで NGO のメッセージテーブルが空になる。 症状はクライアントの接続拒否で、ログに
Allowed Count: 0が出ます。根本原因にたどり着くまで大きく遠回りしました。Steamworks.NET はドメインリロードのたびにSTEAMWORKS_NET定義シンボルを自動で追加しますが、こちらはテストのたびにgit checkout ProjectSettingsで設定を掃除してシンボルを消していたため、次に Play に入るとまた追加されて再コンパイルが走っていました。シンボルをリポジトリにコミットして解決しました。 - 共有アカウントの競合。 同じ国の 2 人のプレイヤーは財布と倉庫を共有します。クライアントのセル操作コマンドは座標しか持たないので、チームメイトが倉庫を触った直後だと、別のものを売ってしまう可能性がありました。今はコマンドに「自分に見えているものは何か」を含め、ホストが照合して、一致しなければ拒否してプレイヤーに知らせます。拒否の種類ごとに明確なメッセージも出し、黙って失敗することはなくなりました。
テストは、エディターをホスト、ビルド版をクライアントにして、ローカルループバックで行います。クライアントにはコマンドライン引数の一式(-polysea-net-test など)があり、実際のゲーム関数を通して売買、任命、接舷などの操作を実行します。Tools/NetTest には 20 近いシナリオスクリプトがあり、ネットワーク形式が変わるたびにプロトコルのバージョンを上げ、ハンドシェイクでは数値テーブルのフィンガープリントも比較するので、両者の数値が一致しないと接続できません。
接舷戦:手触りの代わりに自動演習
10 月 15 日、接舷戦を個々の兵を操作するリアルタイム戦術に変更しました。アニメーション、パスファインディング、士気、29 種の戦術アイテムがすべて甲板上で実際に機能する必要があり、人の手ではテストしきれないので、BoardingRehearsal を書きました。エディターでランダムな戦闘を連続実行し、死因、スタックしたユニット(スクリーンショット付き)、アイテムが実際に効いたかを記録します。その場走り、足滑り、キャラクターの重なりはすべてカウント指標にしており、72 戦以上の結果は、その場走り 0、足滑り 0、重なり 0〜2 組、エラー 0 でした。
バランス調整もここから始まりました。同じブリッグ、同じ人数での味方の勝率は、イングランド相手に 75%、スペイン 94%、海賊 44%、スケルトン 0%。ところが UI に表示される戦力は 4 陣営とも約 430 で、戦力の式が陣営ごとの武器とスケルトンが潰走しないことを計算に入れていないとわかりました。修正後、実測の勝率で戦力の係数を校正し、自動解決と手動戦闘の勝率を一致させました:
| 相手 | 自動解決の勝率 | 実測の勝率 |
|---|---|---|
| イングランド | 48% | 47% |
| スペイン | 81% | 81% |
| 海賊 | 36% | 34% |
| スケルトン | 29% | 31% |
この曲線はとても急です。スケルトンの HP 係数を 0.7 から 0.68、さらに 0.65 へ変えると、味方の勝率は 25% から 31%、さらに 56% へ変わります。人数が 4 人少ないと(24 対 28)、32 戦全敗です。
サンプル数の教訓もここから得ました。ある 16 戦の結果は攻撃側 34%、防御側 63% で、防御側が圧倒的に有利に見えましたが、乱数シードを変えると攻撃側 56%、防御側 38% になりました。16 戦ではこの種の差は判断できません。
規模については、プロデューサーが 1 つのルールを決めました:1 人は 1 体のキャラクターであり、絶対にまとめない。各陣営のフィールド上の上限は水夫 64 人と兵士 10 人で、残りは予備隊とし、一定数まで減ると 8 人ずつ投入します。カクつきを避けるため、生成は 1 フレームに最大 2 体です。最適化後は生きている 1 人あたり約 0.1 ms で、死体は毎フレームのループに入らず、フィールドには最大 80 体を残します。
羊皮紙の海図:ランダムマップに手描きする

未探索の海域は手描きの羊皮紙の海図です。難しいのは、マップがシードからランダム生成されるので、海岸線も建物も事前に描けず、マップごとに実行時に生成するしかない点です:
- 地形。 陸地レイヤーを真上から 1 回レンダリングします。すべてのメッシュをマスクシェーダーに置き換えて水面下 1.2 m で切り取り、さらに -8 m でもう 1 回レンダリングして浅瀬を得ます。結果はリニアの RG16 テクスチャに保存します。R は海岸までの距離(±48 m)、G は岩の高さまたは浅瀬です。
- 筆致。 シェーダーがこのフィールドを使って岩のハッチング、砂浜の点描、浅瀬の細かい点を描きます。
- 図案。 80 枚の小さな絵(建物 30 種、地形 14 種、島名のモチーフ 36 個)を、小さな SDF ペンがバックグラウンドスレッドでアトラスに描き込みます。約 1.7 秒です。
- 配置。 シーン内のメッシュを名前で分類し、Union-Find で建物 1 棟ずつにまとめ、対応する図案に置き換えます。
ラベルは当初メインスレッドで約 6 秒かかっていましたが、すべて Task に移して解決しました。もう一つ見つけにくい落とし穴があり、データテクスチャは必ずリニアでなければならず、初期の R8_SRGB では島がひと回り小さく描かれました。
ビルド後にしか出ない問題
| 現象 | 原因 | 修正 |
|---|---|---|
| 上陸戦の地面が消え、一面が海になる | Terrain の drawInstanced には GPU instancing を有効にしたマテリアルが必要だが、URP 付属のものは無効で、ビルド時にバリアントが削られる | 自前の地形マテリアルを使う |
| 実行時に作ったオブジェクトがマゼンタになる | CreatePrimitive には URP ビルドで使えるマテリアルがない | 常に明示的にマテリアルを割り当てる |
| 船長視点に霧がない | メインシーンで霧を切っていたので、自動ストリップが霧のバリアントをすべて削除 | ストリップをカスタムに変更し、線形フォグを残す |
これらはエディターではどれも見えません。そこでビルド版の自己テストを追加しました。POLYSEA.exe -batchmode -polysea-autotest <dir> で 1 ゲーム走らせ、上陸戦の自己テストではさらに戦闘画面をレンダリングして、画面中央が暖色の地面か青い海かを判定します。この自己テストはグラフィックス付きで起動する必要があり、-nographics ではピクセルを読めず、誤って失敗と報告します。
エージェントと協業する習慣
- テストとプレイヤーがセーブを共有する。 テストがプロデューサーのセーブを少なくとも 4 回上書きしました。今は Play に入ったらまず
SaveSystem.OverrideFolderをテスト用フォルダーへ向け、読み戻して確認してからゲームを開始します。 - Play モードではスクリプトを変更しない。 再コンパイルで静的変数がリセットされ、さきほどのセーブのリダイレクトも消えます。このルールを書き留めた後も、違反したことがあります。
- テスト後にプロジェクト設定を戻す。 Unity はテスト中に変えたタイムスケールとバックグラウンド実行を
ProjectSettingsに書き戻すので、テスト後に元に戻し、ビルド前に確認します。 - コードの既定値とアセットの値は別物。 C# の既定値を変えても、
GameTuning.assetに保存された古い値が有効なままです。 - 決定はドキュメントに書く。 大きなシステムは先に設計ドキュメントを作り、未決事項はドロップダウンの選択肢にしてプロデューサーが選びます。ドキュメントがそのまま仕様です。
- 踏んだ落とし穴はメモに書く。 コンテキストは圧縮されますが、メモは圧縮されません。現在 90 件ほどあり、1 件に 1 つの事実と、その原因と対処を書いています。
使用量
セッションは 2026-09-23 04:01(日本時間)に開始し、ここでは本記事を書いた 2026-11-01 時点までを集計しています。メインセッションと 440 件のサブエージェントのログを含みます。
| 項目 | メインセッション | サブエージェント | 合計 |
|---|---|---|---|
| モデル呼び出し回数 | 23,731 | 15,756 | 39,487 |
| 出力トークン | 2,713 万 | 1,001 万 | 3,714 万 |
| キャッシュ読み取りトークン | 127.6 億 | 28.9 億 | 156.5 億 |
| キャッシュ書き込みトークン | 1.19 億 | 0.73 億 | 1.92 億 |
| 未キャッシュ入力トークン | 47,810 | 31,522 | 79,332 |
本サイトのこれまでの方式で換算すると(キャッシュ読み ×0.1、キャッシュ書き ×2、出力 ×5)、合計は約 21.3 億トークンです。うち Claude Opus 5.5 の呼び出しが 34,992 回、Claude Sonnet 5.5 が 4,491 回、Claude Haiku 5.5 が 4 回です。
うまくいったこと
- 巻き戻せる状態を保つ。 大きな実験のたびにタグを残したので、方向を覆すコストは低く済みました。
- システムごとに専用の検証ツールを作る。 接舷の演習、マルチプレイのスクリプト、ビルドの自己テスト、港のスタック脱出テスト。これらのツールはどの機能よりも価値がありました。
- 推測の代わりに測定する。 ラウドネス、フレーム時間、勝率、すべて数字で見られます。
- エフェクトを必要な場所に限定する。 うねりは船長視点だけにし、見下ろしの海はシンプルなままです。
うまくいかなかったこと
- ベースラインなしで海面を変更し続け、戻れなくなるまで止まらなかった。
- 固定カメラで動きの問題をテストし、同じ現象が何度も報告されてようやく見つけた。
- 16 戦の勝率で結論を出した。
- テストがプロデューサーのセーブを上書きした。しかも 1 度ではない。
- 最初の巻き戻しで「以前」が具体的にどのバージョンかを確認せず、巻き戻しが 1 回余計にかかった。
次にやること
- 船のクラスごとに数値とモデルを磨く。
- 紙上の経済モデルに沿ってお金の曲線を調整する。
- 多言語化:現在 UI は中国語のみで、新しい UI にはすでに言語中立の記号を使っている。
- ローエンド機向けの戦闘規模設定。
- 賞金任務と酒場の情報のマルチプレイ実地テスト、および劣悪なネットワーク環境でのテスト。この 2 つはまだ実施していません。
本記事は Claude Opus 5.5 がプロジェクトの git 履歴、コード、開発メモから執筆しました。使用量の数字はセッションログを直接集計したものです。