FIELD NOTES

NOTE 02 · AUTHORITY AND WEATHER

決めるのはサーバー、天候は決めない

戦闘判定をサーバー側に置いた理由、天候を表現レイヤーに厳密に閉じ込めた理由、そしてその二つを隔てる防火壁について。

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

一行でいうと

戦闘の結果はサーバーが決める。天候はただの画だ。この二つのあいだには、 越えさせない壁が一枚ある。

以下は、その理由と、その壁の具体的な形。


一、なぜ権威をサーバーに置くのか

Phase 1 は Nakama 3.39.0 でサーバー権威を取る。サーバーロジックは Nakama の TypeScript サンドボックスで動く。このサンドボックスには Node の fscrypto もない。 最初は面倒だと思ったが、結果的には助けになった。決定論的なロジックを いちばん素朴な書き方に強制するからだ。クライアント側(GDScript)が まったく同じ結果を出せなければならない以上、それでちょうどいい。

権威シミュレーションは整数 XZ 座標で、y_mm は固定された高さフィールドから導出する。 なぜ整数か。浮動小数点は二つのランタイム間でビット単位に一致させられないのに、 私は状態ハッシュを突き合わせたいからだ。TypeScript と GDScript の両側で、 計算式、乱数、プロトコルのシリアライズ、状態ハッシュにゴールデンベクタが付いていて、 両者の結果は完全に一致しなければならない。

P1-WP1 はそのためのラボだった。隔離された、権威移動だけを扱うスライス。 30 Hz の移動プロトコル、リアルタイムセッション/ソケットの単一所有者、 クライアント予測と和解。走り終えたあと、不変の証拠として封印されている。

数字はこうだ(すべて封印済み証拠から。あとから見積もった値ではない)。

  • 正式プロファイルの join RTT 中央値 / P95:252.124 / 465.155 ms
  • 劣化プロファイル(200 ms 遅延、50 ms ジッタ、3% パケットロス): 3554.517 / 7935.412 ms
  • Snapshot 上限は 5,120 バイト。見栄えのために小さい数字を捏造してはいない;
  • 三つの WSS プロファイルはそれぞれ 610 秒の測定を完走し、CA 検証付き WSS の クロスバインド、tick、帯域、正確なリリース、クリーンアップ、そして StableError / ハッシュ / プロトコル / ソケット / シーケンス失効の厳密なゼロチェックを通した。

劣化プロファイルの数字はひどい。本当の値なので、そのまま残している。


二、失敗も証拠に残す

最初の v2 劣化プロファイル 20260716T222636Z_degraded_200_50_3_67129 は 610 秒を完走したが、 両方のクライアントが Event を一つも受け取らなかった。 ファイナライザはこのランをそのまま拒否して消去した。StableError も Match 側の原因も でっち上げなかった——当時は本当に推定できなかったからだ。

同一ソースの再試行は通ったが、それは古いソーススナップショットに属するので、 最終集計からは除外されている。

もう一件はもっと妙だった。正式プロファイル 20260716T231707Z_formal_120_20_1_21821 は測定を二回とも完了し、 クライアントは二つとも終了コード 0 だったのに、締めのところで 「読み取れないリリース ACK」を理由に拒否された。

ローカルの読み取り専用証拠からは、macOS の com.apple.provenance 拡張属性が遅れて付き、 ACK ファイルの ctime がペイロードの mtime より後になった、という疑いが強い。

しかし——この汎用的な失敗そのものは、その低レベルの原因を証明しない。 だから閉じた二つの ACK に限定して、最小限の強化だけを入れた。 ファイルあたり最大 3 秒、少なくとも 250 ms 間隔での完全な厳密読み取りを 2 回、 ドキュメント内容・バイト・モード・ハッシュ・デバイス・inode・サイズ・mtime・ctime の すべてが等しいこと。欠落・不安全・部分書き込み・変化し続ける ACK は今も即座に失敗する。 それ以外の JSON は単一の厳密読み取りのままだ。

そしてソースを変えたので、五つの正式プロファイルは全部走り直した。

これを書き残すのは、「たぶんこれが原因だからこう直した」と「原因を証明した」が 別物であり、それを混ぜるのがこのプロジェクトでいちばん危険だからだ。


三、天候にできること、できないこと

現在、リアルタイムに切り替えられる天候表現は三つある。 warm_day(既定)、rainy_daywindy_day

これらは設定駆動の表現レイヤープロファイルで、それぞれ一つの JSON、 宣言されたステータスは additive_presentation_only だ。

が変えるもの:

  • 空、太陽の色と強度(sun_energy は暖かい昼から 0.42 へ)、環境光、霧の色と密度;
  • ボリューメトリックフォグ、露出、明度/コントラスト/彩度の調整(彩度は 0.8 へ);
  • 近景の雨脚 440 本、遠景 220 本、地面の水しぶき 64 箇所。すべて固定シード 104729
  • 地表の濡れ:法線オフセット、色味、ラフネス 0.18、スペキュラ 0.72、フレネル強度 0.16

が変えるもの:

  • ワールド空間の風の筋(近景 72 本、遠景 36 本、固定シード 130363);
  • 樹冠の揺れ:12 個の樹木 MultiMesh、風向 XZ (1.0, 0.28)、強度 0.18 m、速度 1.35、 突風周波数 0.55、フラッター周波数 2.8

制約は同じ JSON の constraints に機械可読で書いてある。

"constraints": {
  "default_weather_remains": "warm_day",
  "gameplay_authority_affected": false,
  "terrain_collision_or_navigation_affected": false,
  "town_or_title_affected": false,
  "tree_trunks_animated": false,
  "tree_shadow_casters_animated": false
}

はっきり書いておく。

  • 天候は地形、コリジョン、ナビゲーション、AI の知覚、ダメージ、戦術ルール、 オンライン権威を変えない
  • 自動の天候サイクルはない。ランダムな天候イベントもない。 Nakama 権威による天候同期もない
  • 幹は動かない。影を落とす木も動かない——揺れているのは樹冠の葉だけだ;
  • いまの切り替え入口は開発者ビルドのデバッグドロワーにある (zh_CN / ja / en の文言もテストも揃っている)。 通常のプレイヤー UI には出していない

公式サイトの晴/雨/風スイッチャーは表現レイヤーのプレビューだ。 見えているとおりのもので、同じ戦場・同じカメラ・同じキャラクター配置を、 三つの天候表現で並べた比較にすぎない。

「動的天候が地形と戦術を作り変える」と書いたほうが景気がいいのは分かっている。 でもそれは現在の実装ではないので、そうは書かない。


四、性能:達成したものと、していないもの

P0B のライティング V3、desktop_high において:

  • 密度 20:GPU 中央値 / P95 18.000 / 22.420 ms
  • Boss + 19:19.670 / 24.612 ms
  • Gate-C ビジュアルの Metal GPU P95 上限は 33.3334 ms(1280×720、desktop_high)——通過。

しかし Phase 1 にはより厳しい目標がある。<16.67 ms。こちらは未達のままだ。

通過でも免除でもなく、P1-B へ延期されている。最終的なパッケージ/混合負荷での証明は P1-D より後にはできない。ステータス文書でこれを一行に切り出しているのは、 「全体的に性能は問題ない」という言い方に飲み込まれないようにするためだ。

安定フレームレートの主張もしていない。GPU サンプルは GPU サンプルであって、 プレイヤーが感じる滑らかさとは別物だ。


五、戦闘そのものの現状

  • プレイヤーアクション:移動、オートスラッシュ、ダッシュスラッシュ、旋風斬、回避;
  • 敵:決定論的な Goblin と ordinary Minotaur の AI、有界の攻撃スロットと分離挙動;
  • 密度:正式ピーク 20 体、ストレス 30 体;
  • ボス:三フェーズ、予兆、共有攻撃者スロット、ポイズ、有界の増援;
  • フィードバック:ダメージ、スタガー、ノックバック、死亡、ダメージ数値、VFX、SE、HUD;
  • プール:敵/エフェクト/音声の固定プール。5 分間のソークで枯渇ゼロ、 フォールバックゼロ、ノード/オーファン増加ゼロ。

オンライン部分(P1-WP2)の承認範囲は、プライベート Hub のパーティコード / リーダー / Ready / パーティチャット、権威あるオンラインのソロ/デュオ森林戦闘、 サーバー側の AI / コリジョン / スキル / ダメージ / アップグレード / ボス / 死亡 / 観戦 / 精算、60 秒以内の観戦のみの再接続(操作の引き継ぎなし、途中参加なし)、 全か無かのロック済みロースター精算とレシート/再起動リカバリ。

そして P1-B で必ず止まる。これは自分に引いた線だ。


戦闘はまだ反復中だ。4 人の部分、精算のディテール、ボスの手触りはどれも動いていて、 この記事が書いているのは現在の状態になる。