**Show HN:Moongate – .NET 10 で構築した Ultima Online サーバーエミュレーター(Lua スクリプティング対応)**

2026/03/06 23:22

**Show HN:Moongate – .NET 10 で構築した Ultima Online サーバーエミュレーター(Lua スクリプティング対応)**

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

Moongate v2 は .NET 10 をベースに構築された次世代 Ultima Online サーバーで、クリーンでモジュラーなアーキテクチャ、決定論的ゲームループ処理、および堅牢なパケットツールを重視しています。起動時には TCP を使用し、フレーム/パケットを解析し、属性ベースのソース生成(

[PacketHandler(...)]
)でマッピングし、インバウンド/アウトバウンドイベントバスを介してトラフィックをルーティングします。また、柔軟なサーバーロジックのために Lua スクリプティングもサポートしています。埋め込み HTTP ホストはヘルスチェックと管理エンドポイントを公開し、永続化は軽量なファイルベースモデル(
snapshot.world.snapshot.bin + append‑only journal.world.journal.bin
)で行い、MessagePack‑CSharp ソース生成でシリアライズされます。起動時にスナップショットを読み込み、その後ジャーナルを再生します。

空間ストリーミングはセクタ/チャンク戦略に従っており、16 × 16 のセクタが設定可能な半径の周囲で遅延ロードされ、メモリ増大と CPU 使用量を制限します。サーバーには対話型コンソール UI、パフォーマンス指標用タイマウィール、および Scriban/Smtp を利用したメールスタックが含まれています。

設定はすべて

MOONGATE_
プレフィックス付き環境変数で上書き可能で、二重アンダースコアを使用したネストされたプロパティもサポートしています。Docker イメージ(
tgiachi/moongate:latest
)は Native AOT バイナリを提供し、Prometheus/Grafana を用いた監視スタックが
stack/
ディレクトリに配置されています。

Moongate は ModernUO、RunUO、および ServUO などのレガシープロジェクトからインスピレーションを得ていますが、独自のコードベースです。GitHub の issue/discussion や Matrix チャットルーム(https://matrix.to/#/#moongate:matrix.org)を通じて貢献者を歓迎しています。本プロジェクトは GPL‑3.0 の下でリリースされ、ビルド・テスト・カバレッジ分析・セキュリティチェックの CI パイプラインが整備されています。

現在サポートしているパケットにはログイン/認証、移動、アイテム相互作用、スピーチ/チャット、ターゲティング、およびさまざまなアウトバウンドゲームプレイパケットがあります。Lua サブシステムはモジュール、関数、イベントフック、視覚効果、Gump フロー、スクリプト ID ディスパッチ、NPC ブレインループをサポートし、

moongate_data/scripts/
からスクリプトをロードします。

今後の作業計画としては、パケット処理の拡張、空間ストリーミングの改善、スクリプティング機能の追加、および Docker イメージと Prometheus/Grafana ダッシュボードを通じた監視強化が挙げられます。GitHub の pull request による貢献は積極的に奨励されています。

本文

Moongate v2 ― .NET 10で構築されたモダンな Ultima Online サーバーです。

クリーン・モジュラーアーキテクチャ
• 強力なパケットツール
• 決定論的ゲームループ処理
• 実用的なテストカバレッジ


コラボレーション

現在、コードレビューや技術指導を特に歓迎しています。

Moongate は ModernUO、RunUO、ServUO などのクローンではなく、あくまでそれらからインスピレーションを得たものです。


謝辞

プロジェクトリンク
POLServerhttps://github.com/polserver/polserver
ModernUOhttps://github.com/modernuo/modernuo

データクレジット

  • ワールド装飾(Assets/data/decoration/**) – ModernUO 配布物から
  • ワールド位置情報(Assets/data/locations/**) – ModernUO 配布物から
  • 看板データ(Assets/data/signs/signs.cfg) – ModernUO から適応

プロジェクト概要

目標

  • 正確さとイテレーション速度に重点を置いた保守性の高い基盤
  • 明示的でスレッドセーフなネットワーク & ゲームループ境界
  • ソース生成された登録付き型安全プロトコルパケット
  • AOT にも対応しつつローカル開発フローを維持

ストーリー

背景は https://orivega.io/moongate-v2-rewriting-a-ultima-online-server-from-scratch-because-i-wanted-to/ をご覧ください。

フロントエンド

UI は

ui/
にあり、アイテムテンプレート検索と画像プレビューが可能です。


現在の状態

実装済みの主要機能は以下の通りです。

項目詳細
NetworkingTCP 起動、パケットフレーミング/解析、
[PacketHandler(...)]
ソース生成、入出力イベントバス。
Game Loopタイムスタンプ駆動、タイマウィール、オプションのアイドルCPUスロットリング。
Lua Scripting実行時エンジン、モジュール/関数バインディング、
.luarc.json
生成。
Persistenceスナップショット + ジャーナル(MessagePack‑CSharp)、ファイルロックモード、スレッドセーフリポジトリ。
EmailScriban テンプレートを備えた最小 SMTP スタック。
TemplatesJSON アイテム/モビールテンプレート;実行時に仕様解決。
Metrics & HTTP埋め込み ASP.NET Core ホスト、OpenAPI / Scalar ドキュメント、Prometheus メトリクス。
Background Jobsスレッド外作業用
IBackgroundJobService
と安全なコールバック。

ハイライト

  • ソース生成されたパケットテーブルとリスナー登録(AOT フレンドリー)。
  • 空間セクタ/チャンクストリーミング;予測可能なメモリ使用量。
  • ドアジェネレーター (
    .spawn_doors
    ) は Lua でライブオープン/クロージング。
  • ライトサイクルは
    ILightService
    に分離。

アーキテクチャ

src/
 ├─ Moongate.Server          – ブートストラップ、ゲームループ、ネットワーク
 ├─ Moongate.Network.Packets – パケット契約とレジストリ
 ├─ Moongate.Generators      – ソースジェネレーター(パケット、ハンドラー、メトリクス)
 ├─ Moongate.UO.Data         – ドメイン型・ユーティリティ
 ├─ Moongate.Core            – 共有ヘルパー
 ├─ Moongate.Scripting       – Lua エンジン & モジュール
 └─ Moongate.Server/Http     – 埋め込み ASP.NET Core ホスト
tests/  – 単体テスト
benchmarks/ – BenchmarkDotNet スイート
docs/   – ドキュメント、計画、プロトコルノート

ソースジェネレーター(AOT)

  • パケットテーブル/レジストリ生成。
  • [RegisterPacketHandler]
    [RegisterGameEventListener]
    を登録。
  • ファイルローダー登録 (
    [RegisterFileLoader(order)]
    )。
  • メトリクススナップショットマッパー。
  • スクリプトモジュールレジストリ (
    [ScriptModule]
    )。
  • バージョンメタデータ。

イベントとパケットの分離

  1. IPacketListener – 入力パケット → ドメインユースケース。
  2. IGameEventBusService – ドメインイベントを発行。
  3. IOutboundEventListener – イベントを受けて送出パケットキューへ投入。

ゲームループは

IOutgoingPacketQueue
IOutboundPacketSender
を消費。


コマンドシステム

パス説明
C# ビルトイン
[RegisterConsoleCommand]
ICommandExecutor
動的 / Lua
commandSystemService.RegisterCommand(...)

認可

  • コンソール:常に Administrator。
  • インゲーム:セッションの
    AccountType
    を使用。

例(C# コマンド):

[RegisterConsoleCommand(
    "whoami|me",
    "Shows basic identity information.",
    CommandSourceType.Console | CommandSourceType.InGame,
    AccountType.Regular)]
public sealed class WhoAmICommand : ICommandExecutor
{
    public Task ExecuteCommandAsync(CommandSystemContext ctx)
        => ctx.Print("You are connected.");
}

Lua スクリプト

  • LuaScriptEngineService
    が実行、定数、コールバックを管理。
  • [ScriptModule]
    でモジュール登録。
  • スクリプトは
    moongate_data/scripts/**
    に配置し、
    .luarc.json
    は起動時に生成。

例(コールバック):

function on_player_connected(p)
    log.info("Player connected")
end

視覚効果(Lua)

local npc = mobile.get(0x00000030)
if npc then npc:SetEffect(0x3728, 10, 10, 0, 0, 2023) end

effect.send_to_player(0x00000022, 3613, 2585, 0, 0x3728, 10, 10, 0, 0, 5023)

アイテム ScriptId ディスパッチ

フックLua 関数
single_click
on_click
double_click
on_double_click

アイテムテンプレート例:

{
  "type":"item",
  "id":"healing_potion",
  "name":"a healing potion",
  "itemId":"0x0F0C",
  "scriptId":"items.healing-potion"
}

Lua テーブル

items_healing_potion
on_click
,
on_double_click
を含むことができる。


ガンプ(Gumps)

  • ファイルベース:レイアウトテーブル (
    moongate_data/scripts/gumps/*.lua
    )。
  • ランタイムビルダー
    gump.create()
    /
    gump.send(...)

例ファイル:

return {
  ui = { ... },
  handlers = {
    open_next = function(ctx) log.info("Button clicked") end
  }
}

永続化

  • スナップショット (
    world.snapshot.bin
    ) + ジャーナル (
    world.journal.bin
    )。
  • MessagePack‑CSharp によるソース生成シリアライズ。
  • 操作ごとのチェックサムで整合性保証。
  • ファイルロックモード(デフォルト有効)。

起動時:スナップショットを読み込み、ジャーナルを再生。
実行中:ジャーナルへ追記;保存/停止時に新しいスナップショットを書き出し、ジャーナルをリセット。


メール

最小 SMTP スタック:

サービス役割
IEmailService
オーケストレーション
IEmailTemplateService
Scriban レンダリング
IEmailSender
SMTP 実装 (
SmtpEmailSender
)

テンプレートは

moongate_data/email/templates/**
に配置。


設定

すべての設定は

MOONGATE_
プレフィックス付き環境変数で上書き可能。
ネストプロパティはダブルアンダースコア
__
を使用。

例:

MOONGATE_HTTP__PORT=8088
MOONGATE_SPATIAL__SECTOR_ENTER_SYNC_RADIUS=3

サポートグループ:Core, HTTP, Game, Metrics, Persistence, Spatial, Scripting, Email。


Docker

./scripts/build_image.sh -t moongate-server:local
docker run --rm -it \
  -p 2593:2593 -p 8088:8088 \
  -v /path/host/moongate-root:/app \
  -v /path/host/uo-client:/uo:ro \
  --name moongate moongate-server:local
  • -it
    で実行すると
    moongate>
    プロンプトが表示。
  • フロントエンドは
    ui/
    からビルドされ、HTTP 経由で配信。

ベンチマーク

ローカル実行例:

./scripts/run_benchmarks.sh --filter '*'

主要指標(サンプル):

ベンチマーク平均割り当て
PacketParsingBenchmark.ParseLoginSeedPacket94.82 ns664 B
SpatialWorldServiceBenchmark.AddOrUpdateMobiles (500)75.939 µs74.56 KB

完全レポートは

BenchmarkDotNet.Artifacts/results/
にあります。


ストレステスト

dotnet run --project tools/Moongate.Stress \
  --host 127.0.0.1 --port 2593 \
  --http http://localhost:8088 \
  --clients 100 --duration 300 --ramp-up-per-second 10

SLO:

  • ログイン成功率 ≥ 99%
  • 予期せぬ切断なし
  • 移動 ACK p95 < 200 ms

コントリビューション

  1. リポジトリをフォーク。
  2. フィーチャーブランチ作成。
  3. dotnet test
    を実行し、すべてのテストがパスすることを確認。
  4. 明確な説明付きでプルリクエストを送信。

すべてのコードはプロジェクトのコーディング規約に従い、適切なテストを含める必要があります。


ライセンス

GNU General Public License v3.0 – 詳細は LICENSE をご覧ください。

同じ日のほかのニュース

一覧に戻る →

2026/03/07 6:52

「このCSSは、私が人間であることを証明します。」

## Japanese Translation: (以下に翻訳文を記載します) **著者は、選択的な大文字化、CSS を用いた対象的なケース変換(`text-transform: lowercase`)、慎重に使われる em ダッシュなどの微妙なタイポグラフィック・選択が、ファイルを `tr` でパイプするような鈍い自動化手法よりも優れていると主張しています。大文字化は「最初の傷」として描かれますが、実際には予想ほど痛みを伴わず、必要に応じて単語が大文字で流れ出します。著者は粗末な `cat post.md | tr A‑Z a‑z | sponge post.md` の手法を却下し、よりクリーンな効果を持つ CSS を推奨しています。 em ダッシュは貴重とされますが、作家の真実の自分を露呈させないように隠したままである必要があります。モノスペースフォントはテキストの美学を損なうため拒否されています。小さなスクリプト(`uv run rewrite_font.py`)は文字形態を微調整するための取るべき手段として強調され、意図的に単語を誤字すること(例: “their/there”、 “its/it’s”)がスタイルの一部であると述べられていますが、“Definately?” のような問題のあるペアは避けるとしています。 作家はノーリグ・コーパスを参照し、単語選択を導くとともに、ターゲットとなる単語から「u」を迅速に除去することで手法の精密さを示しています。全体的なトーンは、書くことが外見だけでなく思考・推論・関与を反映するものであると強調しています。 以前の拒否(“No. Not today.”)はスタイリスティックオーバーホールへの抵抗を示しています。次に計画されている変更は、作家自身の自我感覚を変える唯一の真に重要なステップとして描かれています。 技術的読者――特に文書スタイリング、フォント操作、編集ワークフローに関わる人々――に対して、このメッセージは自動変換から離れ、意図的なタイポグラフィック決定へ移行することを奨励し、デザイン標準や編集実務の再構築につながり得ると述べています。

2026/03/07 7:55

**C# の文字列が Dapper で SQL Server インデックスを静かに破壊する理由** Dapper を使って SQL Server データベースへクエリを投げる際、文字列結合や文字列補間(string interpolation)でクエリを作成することはよくあります。 しかし、この一見無害な手法がインデックスの性能を黙って破壊してしまうケースがあります。 --- ## なぜ起きるのか 1. **暗黙の型変換** `string` と `int`・`bool` など非文字列型を結合すると、SQL Server は列値を `nvarchar` に変換せざるを得ません。 2. **インデックス回避** この暗黙変換により最適化器は既存の数値や日付インデックスを利用できず、フルテーブルスキャンが発生します。 --- ## 問題を引き起こす典型的なパターン | パターン | 何をしているか | インデックスへの影響 | |---------|-----------------|----------------------| | `WHERE Id = " + id`(文字列結合) | `Id` 列を `nvarchar` に変換 | フルスキャン | | `$"SELECT * FROM Users WHERE IsActive = {isActive}"`(補間) | ブール値も同様に `nvarchar` へ変換 | フルスキャン | | `WHERE CreatedDate >= @date.ToString()` | 日付を文字列へ変換 | インデックスが失われる | --- ## 修正方法 1. **インライン値ではなくパラメータを使用する** ```csharp var sql = "SELECT * FROM Users WHERE Id = @Id"; connection.Query<User>(sql, new { Id = id }); ``` 2. **型の一貫性を保つ** 列が期待する正確な型(`int`、`DateTime` など)で渡す。 3. **C# で暗黙変換を避ける** 必要なら明示的にキャストまたは変換し、安全かつ意図した変換のみ行う。 --- ## 簡易チェックリスト - [ ] Dapper に渡す値は文字列化せず、型付きである。 - [ ] 変数データと SQL フラグメントのインライン結合を行わない。 - [ ] すべてのクエリに `@ParameterName` プレースホルダーを使用する。 これらのガイドラインに従えば、インデックスの整合性を保ちつつクエリを高速かつ効率的に維持できます。

## Japanese Translation: **概要:** .NET/Dapper アプリケーションでは、C# の文字列を `nvarchar(4000)` として渡すと、SQL Server が `varchar` 列に対して暗黙の型変換(implicit conversions)を実行します。これにより、インデックス検索がスキャンに置き換わり、論理読み取り数が単桁から数万に膨らみ、CPU/I/O の使用率が急増します(例:`CONVERT_IMPLICIT(nvarchar(255), [Sales].[ProductCode], 0)`)。 正確性には影響しませんが、実行計画や Query Store の警告で明らかになります。特に `SQL_Latin1_General_CP1_CI_AS` などの照合順序では顕著です。 **修正:** パラメータを ANSI として明示的に宣言し、列サイズと一致させます。 ```csharp var p = new DynamicParameters(); p.Add("productCode", productCode, DbType.AnsiString, size: 100); await conn.QueryFirstOrDefaultAsync<Product>(sql, p); ``` または匿名オブジェクトを使用する場合: ```csharp new { productCode = new DbString { Value = productCode, IsAnsi = true, Length = 100 } } ``` スキーマ変更、インデックス更新、クエリ書き換えは不要です。パフォーマンスの改善は即座に実感できます。 **監査ヒント:** Query Store で `@nvarchar(4000)` を検索し、varchar 列へ文字列を渡す匿名オブジェクトをコード内でスキャンしてください。 **ベストプラクティス:** 将来のリグレッション防止のために、`DbType.AnsiString`(または `IsAnsi = true`)を使用した理由をコメントしておくことが推奨されます。 この簡単な調整でサーバー負荷を低減し、コスト削減とスケールアップが実現します。結果として開発者・ユーザー・広範な .NET/SQL Server コミュニティ全体に恩恵をもたらします。

2026/03/07 6:19

**IPリースの陰影ある世界**

## Japanese Translation: IPリースは、誰もがクリーンで匿名のIPv4アドレスを取得し、ジオロケーションデータを操作できる隠密レンタル市場を生み出しており、IPベースの評判システムの信頼性を損なっています。ブロックを保有し、標準的な地域インターネットレジストリ(RIR)手順外でサブリースすることで、これらのサービスはWHOISトレーサビリティとRIRアカウンタビリティチェーンを迂回します。リース会社は料金を払ってブラックリスト化された範囲を「クリーン」し、ドロップダウンまたはCSVアップロードで任意の住宅または商業ジオロケーションを割り当て、WHOIS国フィールドまで操作することができ、MaxMind、Cloudflare、Googleなどに偽情報を供給します。 主要なVPNおよびプロキシプロバイダー(例:NordVPN、ExpressVPN、CyberGhost、PIA)は、LogicWeb、IPXO、INIZ、IPFoxi、Heficed、AnyIP/IPv4Deals などのリース会社からIPを調達しています。これらのプラットフォームは多くの場合、同じサイトで生IPスペースと完全なプロキシサブスクリプションの両方を販売し、エンドツーエンドの匿名化パイプラインを構築します。一部のプロバイダーは住宅ISPと直接提携してトラフィックを実際の加入者ネットワーク経由でルーティングし、合法的なオペレーターとプロキシとの境界を曖昧にしています。 業界は法的グレイズゾーンで運営されており—IPリースやジオロケーション操作を明示的に禁じる法律がないため—実効性の低い執行とインセンティブの不整合が生じています。リースが拡大するにつれて、インターネットセキュリティの基盤であるIP評判リスト、WHOIS帰属、およびジオロケーションデータベースはさらに侵食され、大規模な位置・所有権・評判の偽装が可能になります。これはユーザー(ボット検出やレート制限での誤検知)とIPベースのセキュリティ決定に依存する企業を脅かし、最終的にはインターネット全体の説明責任と安全性を弱めます。

**Show HN:Moongate – .NET 10 で構築した Ultima Online サーバーエミュレーター(Lua スクリプティング対応)** | そっか~ニュース