FIELD NOTES

NOTE 01 · BUILDING THE FOREST

森を、検証できるものとして作る

地形をどうベイクしたか、遠景をなぜ切り離したか、そして町がなぜ戦場より難しいのか。

以下は開発者個人の記録で、内部マイルストーン、技術判断、失敗の経緯を含みます。現在の状態を述べるものであり、最終形でも、リリースの約束でもありません。

森は「見栄えが良くなった」では完了しない

このプロジェクトでは、ワールドが二つの身元を持っている。

一つは目に見える森。光の筋、苔、水音、遠くの樹冠。 もう一つはオンライン権威から見た森。固定された高さフィールド、セマンティックハッシュ、 整数座標の集合。

この二つは分けておかないとオンラインが壊れる。だから森に手を入れるときは毎回、 まず「いま動かしているのはどちらか」に答えることから始まる。


一、地形はベイクされたもので、エディタで並べたものではない

addons/terrain_pipeline/ は決定論的なオフラインのベイクチェーンだ。出力は固定されている。

  • 連続した 20×20 m の地形メッシュ;
  • 共有のマテリアルウェイトと AO マスク;
  • 簡略化されたコリジョン;
  • 連結されたナビゲーション;
  • 42 枚のスラブ、38 の森インスタンス、28 の遺跡インスタンス;
  • 26/26 の Sector MultiMesh がシリアライズ済みバッファを持ち、108 のトランスフォームが すべて有効で空間的に重複しない。

なぜこの数字を文書に書くのか。これが「今回の変更が何かを壊していないか」を判断する唯一の根拠だからだ。 地形は手で並べたものではなく、スクリプトが出したもので、同じ入力からは同じ結果が出なければ、 このチェーン全体に意味がない。

v1 のこれらのファイルはハッシュで固定され、不変だ。v2 をやるということは、 新しい *_v2.gd ファイル、新しいランタイムオプション、専用の検証器、専用の証拠ディレクトリを 作るということで、古い方は 1 バイトも触らない。


二、地形セマンティックハッシュと、その防火壁

オンライン権威は地形 v1 のセマンティックハッシュ c1e82575… に固定されている。 これは P1 のコンテンツバンドルに封じられていて、コンテンツバンドルハッシュ 09a0de4a…、 森 54ccd449…、町 411b6aaa… と一緒にオンライン ID を構成する。

つまり地形 v2 は表現レイヤーと P0B のオフラインコリジョン用であって、P1 の権威を変えられない。 本当に差し替えるなら、それは別途承認されたコンテンツバンドルの再構築であって、 「ついでにアップグレード」ではない。

このハッシュは三か所に打ち込まれていて、一致していなければならない。

  • p0b_combat_slice_v1.gd のバックエンドテーブル;
  • p0b_combat_slice_v1_structure_test.gd
  • scripts/automation/validate_p0b_combat_slice_v1.py

三つがずれれば検証器がそのまま落ちる。この制約を入れたのは、自分が将来必ず忘れると確信しているからだ。


三、マップが端で切れてはいけない

森の戦場の合法な境界は 25 m。ゲームプレイ上は問題ないが、画としては良くない。 ある位置で世界が急に終わり、その先に何もないのが見えてしまう。

そこで純表現の遠景レイヤーを足した。戦場側は 192×192 m の不透明な地面の続きで、 固定された 64×64 m の表現地形の下と外に敷かれる。さらに有界の透明な森の地平線アークが プロダクションカメラの焦点に追従し、地面の続きの方はワールド固定のまま。 町側は曲面の、ミップマップ付き透過の森の幕で、既存の TownPresentation サブツリーに入っている。 --p1-town-presentation=off でレイヤーごと消える。

これらの遠景メッシュはすべて影と GI を切り、異方性ミップマップフィルタを使う。 コリジョン、ナビゲーション、ラベル、権威状態、プロトコル状態、ゲームプレイ効果は 一切含まない

テクスチャは生成物だ(戦場 1024×1024 不透明、町 2048×768 アルファ付き)。 ここで私が気にしている規律が一つある。唯一の正のスタイル入力は、ハッシュ付きのワールド基準画像だけだ。 ランタイムのキャプチャは、カメラ・スケール・マップ端の診断入力としてバイト保存されるが、 正のスタイルやパレットの入力ではない。細かい区別に聞こえるが、 これがスタイルが少しずつ流れていくかどうかを決める。

町のテクスチャは下端が完全に不透明でなければならない(透明ピクセル 0/2048)。 さらに高彩度マゼンタの滲みチェックを通す必要がある。透過の抜きは端に色縁を残しやすく、 その端がちょうどプレイヤーの視線の高さに来るからだ。


四、町は戦場より難しい

戦場は境界があり、制御でき、一度に一つのことを解けばいい。町はそうではない。

町では同時に、複数の旅人が居合わせ、名前はバイトをそのまま保って表示され (Unicode が飲み込まれてはいけない)、チャットはプレーンテキストとして描画され、 キャラクターが木の裏に回ったら樹冠がフェードし、ライティングは可読性を保ち、 その上で性能が落ちてはいけない。

樹冠のフェードは特に厄介だった。カメラのオクルージョンフェーダーと地形シーンのあいだには 硬い契約がある。Modules サブツリーは MultiMesh、TRANSFORM_3D、カラー有効、 カスタムデータなし、単一サーフェスの StandardMaterial3D でなければならない。 地形 v2 が実装を変えるのは構わないが、このノード名と形は保たなければならない。 さもないとフェードは静かに効かなくなる——エラーは出ず、ある日キャラクターが木に隠れて見えなくなるだけだ。


五、いま「森が壊れていない」をどう判断しているか

スクリーンショットでは判断しない。スクリーンショットは嘘をつく。 静的キャプチャで被写界深度が更新されていなかったり、無関係な既定値の変更で ライティングが変わっていたりする。判断は検証器を走らせて *_OK が出るかで行う。

  • 起動チェック startup_check.sh
  • 森の戦場のエクスポート検証(遠景レイヤー、樹木オクルージョン、遺跡の透明度といった リソースの正確なハッシュを束ねる);
  • P1 コンテンツ ID の再計算 build_p1_content_v1.py --check
  • 構造テスト。

これらが全部緑になってから画を見る。順序を逆にすると、 きれいなスクリーンショットのために間違った状態を受け入れ続けることになる。


森はまだ育っている。町のディテール、遠景の層、端の可読性はどれも反復中で、 この記事が書いているのは今の姿だ。