{"id":45375614,"url":"https://github.com/moongate-community/moongatev2","last_synced_at":"2026-03-06T18:13:34.845Z","repository":{"id":339205707,"uuid":"1158590653","full_name":"moongate-community/moongatev2","owner":"moongate-community","description":"Moongate is modern Ultima Online server emulator built from scratch in C# with AOT compilation for high performance and nostalgic gameplay experience.","archived":false,"fork":false,"pushed_at":"2026-02-24T16:44:37.000Z","size":5075,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-24T21:04:16.224Z","etag":null,"topics":["high-performance","mmo","mmorpg","modernuo","retrogaming","rpg","runuo","servuo","ultimaonline","uox3"],"latest_commit_sha":null,"homepage":"https://moongate-community.github.io/moongatev2/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/moongate-community.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-15T16:15:51.000Z","updated_at":"2026-02-24T16:39:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/moongate-community/moongatev2","commit_stats":null,"previous_names":["moongate-community/moongatev2"],"tags_count":31,"template":false,"template_full_name":null,"purl":"pkg:github/moongate-community/moongatev2","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moongate-community%2Fmoongatev2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moongate-community%2Fmoongatev2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moongate-community%2Fmoongatev2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moongate-community%2Fmoongatev2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/moongate-community","download_url":"https://codeload.github.com/moongate-community/moongatev2/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/moongate-community%2Fmoongatev2/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29943690,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-28T13:49:17.081Z","status":"ssl_error","status_checked_at":"2026-02-28T13:48:50.396Z","response_time":90,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["high-performance","mmo","mmorpg","modernuo","retrogaming","rpg","runuo","servuo","ultimaonline","uox3"],"created_at":"2026-02-21T16:21:06.600Z","updated_at":"2026-03-06T18:13:34.827Z","avatar_url":"https://github.com/moongate-community.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Moongate v2\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/moongate_logo.png\" alt=\"Moongate logo\" width=\"240\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/platform-.NET%2010-blueviolet\" alt=\".NET 10\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/AOT-enabled-green\" alt=\"AOT Enabled\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/scripting-Lua-yellow\" alt=\"Lua Scripting\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-GPL--3.0-blue\" alt=\"GPL-3.0 License\"\u003e\n  \u003cimg src=\"https://img.shields.io/badge/status-development-orange\" alt=\"Development Status\"\u003e\n\u003c/p\u003e\n\n[![CI](https://github.com/moongate-community/moongatev2/actions/workflows/ci.yml/badge.svg)](https://github.com/moongate-community/moongatev2/actions/workflows/ci.yml)\n[![Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/moongate-community/moongatev2/gh-pages/badges/tests.json)](https://github.com/moongate-community/moongatev2/actions/workflows/ci.yml)\n[![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/moongate-community/moongatev2/gh-pages/badges/coverage.json)](https://github.com/moongate-community/moongatev2/actions/workflows/coverage.yml)\n[![Code Quality](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/moongate-community/moongatev2/gh-pages/badges/quality.json)](https://github.com/moongate-community/moongatev2/actions/workflows/quality.yml)\n[![Quality Gate](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/moongate-community/moongatev2/gh-pages/badges/quality-gate.json)](https://github.com/moongate-community/moongatev2/actions/workflows/quality.yml)\n[![Security](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/moongate-community/moongatev2/gh-pages/badges/security.json)](https://github.com/moongate-community/moongatev2/actions/workflows/security.yml)\n[![Latest Release](https://img.shields.io/github/v/release/moongate-community/moongatev2)](https://github.com/moongate-community/moongatev2/releases)\n[![Latest Pre-release](https://img.shields.io/github/v/release/moongate-community/moongatev2?include_prereleases\u0026label=pre-release)](https://github.com/moongate-community/moongatev2/releases)\n[![Docs](https://github.com/moongate-community/moongatev2/actions/workflows/docs.yml/badge.svg)](https://github.com/moongate-community/moongatev2/actions/workflows/docs.yml)\n[![Release](https://github.com/moongate-community/moongatev2/actions/workflows/release.yml/badge.svg)](https://github.com/moongate-community/moongatev2/actions/workflows/release.yml)\n[![Docker Image](https://img.shields.io/docker/v/tgiachi/moongate?sort=semver)](https://hub.docker.com/r/tgiachi/moongate)\n[![Docker Pulls](https://img.shields.io/docker/pulls/tgiachi/moongate)](https://hub.docker.com/r/tgiachi/moongate)\n[![Docker Image Size](https://img.shields.io/docker/image-size/tgiachi/moongate/latest)](https://hub.docker.com/r/tgiachi/moongate)\n\nMoongate v2 is a modern Ultima Online server project built with .NET 10.\nIt targets a clean, modular architecture with strong packet tooling, deterministic game-loop processing, and practical test coverage.\n\n\u003e Looking for collaborators: I am actively seeking contributors to help build Moongate v2, and I would especially appreciate support with technical/code reviews.\n\u003e Want to help? Open an issue/discussion on GitHub or join Discord:\n\u003e - Issues: https://github.com/moongate-community/moongatev2/issues\n\u003e - Discussions: https://github.com/moongate-community/moongatev2/discussions\n\u003e - Matrix room: https://matrix.to/#/#moongate:matrix.org\n\n\u003e Moongate is not a clone of ModernUO, RunUO, ServUO or any other server, and it does not aim to be. In fact, we owe a great deal of inspiration to these projects. Their legacy and technical achievements are invaluable, and this project would not exist without them. Thank you.\n\n## Acknowledgements\n\nSpecial thanks to the teams and contributors behind these projects, which strongly inspired Moongate:\n\n- POLServer: \u003chttps://github.com/polserver/polserver\u003e\n- ModernUO: \u003chttps://github.com/modernuo/modernuo\u003e\n\nData credits:\n\n- World decoration datasets (`Assets/data/decoration/**`) are imported from the ModernUO Distribution data pack.\n- World location datasets (`Assets/data/locations/**`) are imported/adapted from the ModernUO Distribution data pack.\n- Sign datasets (`Assets/data/signs/signs.cfg`) are imported/adapted from ModernUO data format and content.\n\nThanks to the ModernUO team for making these resources available.\n\n## Index\n\n- [Project Goals](#project-goals)\n- [Project Story](#project-story)\n- [Frontend Preview](#frontend-preview)\n- [Current Status](#current-status)\n- [Spatial Chunk Strategy](#spatial-chunk-strategy)\n- [World Generation Pipeline](#world-generation-pipeline)\n- [UO Feature Support (Current)](#uo-feature-support-current)\n- [Persistence](#persistence)\n- [Email Delivery (Minimal SMTP)](#email-delivery-minimal-smtp)\n- [Templates](#templates)\n- [Solution Structure](#solution-structure)\n- [Source Generators (AOT)](#source-generators-aot)\n- [Event And Packet Separation](#event-and-packet-separation)\n- [Game Loop Scheduling](#game-loop-scheduling)\n- [Requirements](#requirements)\n- [Server Startup Tutorial](#server-startup-tutorial)\n- [Quick Start](#quick-start)\n- [Command System](#command-system)\n- [Scripting](#scripting)\n- [Item ScriptId Dispatch](#item-scriptid-dispatch)\n- [Scripts](#scripts)\n- [Benchmarks](#benchmarks)\n- [Docker](#docker)\n- [Docker Monitoring Stack](#docker-monitoring-stack)\n- [Documentation](#documentation)\n- [Development Notes](#development-notes)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Project Goals\n\n- Build a maintainable UO server foundation focused on correctness and iteration speed.\n- Keep networking and game-loop boundaries explicit and thread-safe.\n- Model protocol packets with typed definitions and source-generated registration.\n- Stay AOT-aware while preserving a smooth local development workflow.\n\n## Project Story\n\nYou can read the background and motivation behind Moongate v2 here:\n\n- \u003chttps://orivega.io/moongate-v2-rewriting-a-ultima-online-server-from-scratch-because-i-wanted-to/\u003e\n\n## Frontend Preview\n\nI hate building frontend myself, so thanks to Codex I started adding a UI layer in `ui/`.\n\n![UI Screen 1](images/ui/ui_screen1.png)\n![UI Screen 2](images/ui/ui_screen2.png)\n![UI Screen 3](images/ui/ui_screen_3.png)\n\nThe UI now also includes Item Templates search with image previews.\n\n## Current Status\n\nThe project is actively in development and already includes:\n\n- TCP server startup and connection lifecycle handling.\n- Packet framing/parsing for fixed and variable packet sizes.\n- Attribute-based packet mapping (`[PacketHandler(...)]`) with source generation.\n- Inbound message bus (`IMessageBusService`) for network thread -\u003e game-loop crossing.\n- Domain event bus (`IGameEventBusService`) with initial events (`PlayerConnectedEvent`, `PlayerDisconnectedEvent`).\n- Outbound event listener abstraction (`IOutboundEventListener\u003cTEvent\u003e`) for domain-event -\u003e network side effects.\n- Session split between transport (`GameNetworkSession`) and gameplay/protocol context (`GameSession`).\n- Unit tests for core server behaviors and packet infrastructure.\n- Lua scripting runtime with module/function binding and `.luarc` generation support.\n- Lua metadata files (`definitions.lua`, `.luarc.json`) generated in configured `LuaEngineConfig.LuarcDirectory` during engine startup.\n- Embedded HTTP host (`Moongate.Server/Http`) for health/admin endpoints and OpenAPI/Scalar docs.\n- Dedicated HTTP rolling logs in the shared logs directory (`moongate_http-*.log`).\n- Snapshot+journal persistence module (`Moongate.Persistence`) integrated in server lifecycle.\n- ID-based persistence references for character equipment/container ownership.\n- Interactive console UI with fixed prompt (`moongate\u003e`) and Spectre-based colored log rendering.\n- Timer wheel runtime metrics integrated in the metrics pipeline (`timer.*`).\n- Timestamp-driven game loop scheduling with timer delta updates and optional idle CPU throttling.\n- Region system adopted from ModernUO (chosen as the most robust baseline), including polymorphic JSON loading via `$type`.\n- Spatial region resolution indexed by sector with deterministic ordering:\n  - higher `Priority` first\n  - then deeper parent/child hierarchy (`ChildLevel`) when priority ties.\n- Region music mapped as typed `MusicName` and resolved by `MapId` + position.\n- Minimal email stack with Scriban templates and SMTP sender (`Moongate.Email`), wired through `IEmailService`.\n- Basic/timid A* pathfinding service is available (`IPathfindingService` / `AStarPathfindingService`) and already used by Lua mobile movement primitives (`MoveTowards`).\n- Light cycle is now isolated in `ILightService`/`LightService` (separate from weather), including global override commands exposed to Lua.\n- Lua command scripts are organized under `moongate_data/scripts/commands/gm` (one command per file, imported from `init.lua`).\n\n## Recent Development Highlights\n\n- Persistence serialization was migrated to MessagePack-CSharp source-generated contracts to resolve NativeAOT runtime instability.\n- Outbound packet sending was split into a dedicated networking thread path to reduce game-loop contention.\n- Spatial/game-loop hot paths received allocation-focused optimizations across login, packet dispatch, event bus, and persistence mapping.\n- Light cycle logic was extracted from `WeatherService` into dedicated `ILightService`/`LightService`.\n- New Lua GM command scripts were added under `moongate_data/scripts/commands/gm` (`.eclipse`, `.set_world_light`, `.teleports`).\n\n## Spatial Chunk Strategy\n\nMoongate uses a sector/chunk-based world streaming strategy instead of a pure range-view scan model.\n\n- World data is indexed by sectors (`16x16`) and loaded lazily.\n- When a sector is touched, Moongate loads entities (items + mobiles) around it in a configurable sector radius.\n- Around player login and sector changes, snapshots are sent using sector radius windows.\n- Sectors are created, populated, and reused in memory; inactive areas stay unloaded until requested.\n\nWhy this choice:\n\n- Predictable memory growth and lower steady-state CPU usage on large worlds.\n- Better cache locality for entity queries and network snapshot generation.\n- Simpler scalability path for high-concurrency shards.\n\nCompared to classic server approaches that rely mainly on repeated range-view scans, this model is intentionally closer to chunk-streaming systems (Minecraft-style): load/unload by sector boundaries with configurable warmup and sync radii.\n\nFor a detailed internal status snapshot, see `docs/plans/status-2026-02-19.md`.\n\n## World Generation Pipeline\n\nMoongate uses a world-generation pipeline based on `IWorldGenerator`.\n\n- Each generator is a named unit (`Name`), orchestrated by `IWorldGeneratorBuilderService`.\n- The builder supports:\n  - full execution (`GenerateAsync()`),\n  - targeted execution by name (`GenerateAsync(\"doors\")`),\n  - optional progress callback (`Action\u003cstring\u003e`) for logs/progress output.\n- Door generation is implemented as `DoorGeneratorBuilder` (`Name = \"doors\"`), with hardcoded scan regions (ModernUO-style) and `CanFit` filtering before accepting candidate placements.\n- Generated doors are persisted as world items and include facing/link metadata for runtime behavior.\n- Doors now support live open/close behavior on double-click through Lua + `DoorService`.\n- ORA LE PORTE SI APRONO!! :D :D\n\nManual trigger:\n\n- Command: `.spawn_doors`\n- Scope: console + in-game admin command\n- Behavior: runs only the `doors` generator and streams progress lines to command output.\n\n## UO Feature Support (Current)\n\nThis section reflects the current server-side implementation status.\n\n### Supported now\n\n- Active inbound packet handlers:\n  - Login/auth: `0xEF`, `0x80`, `0xA0`, `0x91`, `0x5D`, `0xBD`\n  - Character: `0x00`\n  - Movement: `0x02`, `0xC8`\n  - Item interaction: `0x07`, `0x08`, `0x09`, `0x13`, `0x06`\n  - Speech/chat: `0xAD`, `0xB5`\n  - Targeting: `0x6C`\n  - General info multiplexer: `0xBF`\n  - Player status: `0x34`\n  - Ping: `0x73`\n  - Tooltip: `0xD6`\n- `0xBF` subcommands currently wired in runtime:\n  - `0x06` Party System\n  - `0x1A` Stat Lock Change\n  - `0x2C` Use Targeted Item\n  - `0x2D` Cast Targeted Spell\n  - `0x2E` Use Targeted Skill\n- Active outbound gameplay packets include:\n  - Login/session: `0x8C`, `0xA8`, `0xA9`, `0x1B`, `0x55`, `0x82`, `0xB9`\n  - World/entity sync: `0x78`, `0x20`, `0x2E`, `0x24`, `0x3C`, `0x11`, `0x88`, `0xF3`, `0x23`, `0x76`\n  - Movement/time: `0x22`, `0x21`, `0x5B`, `0xF2`\n  - Environment/effects: `0xBC`, `0x4F`, `0x4E`, `0x6D`, `0x65`, `0x54`, `0x70`, `0xC0`, `0xC7`\n  - UI/speech: `0xAE`, `0xB0`, `0xDD`\n\n### Partially implemented\n\n- Protocol model coverage is broader than runtime gameplay wiring:\n  - many packet contracts exist in `Moongate.Network.Packets`,\n  - only the opcodes listed above are currently connected to live handlers/flows.\n- Item pipeline is functional for pickup/drop/equip/container refresh, but advanced cases (full trade/vendor/economy semantics) are still expanding.\n- Lua runtime is integrated (commands, speech, targeting, gump builder), but high-level game systems are still script-surface growth areas.\n\n### Not yet implemented (major areas)\n\n- Full combat loop (swing/spell damage pipeline, notoriety-driven combat rules).\n- Skill system execution and progression.\n- NPC AI, vendors, loot systems, and spawn regions are still evolving; pathfinding currently exists in a basic form and is not yet a full navigation stack.\n- World simulation breadth (housing, boats, advanced map interactions, seasons/weather effects gameplay-side).\n- Economy systems and complete trading/vendor behavior.\n- Full UO protocol listener coverage (many opcodes intentionally unhandled yet).\n\n## Persistence\n\nMoongate uses a lightweight file-based persistence model implemented in `src/Moongate.Persistence`:\n\n- Snapshot file (`world.snapshot.bin`) for full world state checkpoints.\n- Append-only journal (`world.journal.bin`) for incremental operations between snapshots.\n- MessagePack-CSharp (source-generated) binary serialization for compact and fast read/write.\n- Per-operation checksums in journal entries to detect truncated/corrupted tails.\n- Runtime file-lock mode for snapshot/journal handles (`PersistenceOptions.EnableFileLock`, default: enabled).\n- Thread-safe repositories for accounts, mobiles, and items.\n- Mobile/item relations are persisted by serial references:\n  - `UOMobileEntity.BackpackId`\n  - `UOMobileEntity.EquippedItemIds`\n  - `UOItemEntity.ParentContainerId` + `ContainerPosition`\n  - `UOItemEntity.EquippedMobileId` + `EquippedLayer`\n\nRuntime behavior:\n\n- On startup, `IPersistenceService.StartAsync()` loads snapshot (if present) and replays journal.\n- During runtime, repositories append operations to journal.\n- On save/stop, `SaveSnapshotAsync()` writes a new snapshot and resets the journal.\n- With file-lock mode enabled, snapshot/journal handles remain open for process lifetime and prevent concurrent writers.\n\nNativeAOT note (post-mortem):\n\n- We hit an insidious NativeAOT crash (`Segmentation fault: 11`) during persistence save.\n- Root cause: the previous MemoryPack-based snapshot/journal path crashed under AOT in our runtime scenario.\n- Resolution: full persistence serializer migration from MemoryPack to MessagePack-CSharp source-generated contracts (`MessagePackObject`), covering both snapshot and journal payloads.\n- Result: AOT startup + first admin account creation + save cycle now complete without crash.\n\nStorage location:\n\n- Files are written under the server `save` directory (`DirectoriesConfig[DirectoryType.Save]`).\n\nQuery support:\n\n- `IAccountRepository`, `IMobileRepository`, and `IItemRepository` expose `QueryAsync(...)`.\n- Queries are evaluated on immutable snapshots with ZLinq-backed projection/filtering.\n\n## Email Delivery (Minimal SMTP)\n\nMoongate includes a minimal email pipeline:\n\n- `IEmailService`: orchestration entrypoint.\n- `IEmailTemplateService`: template rendering via Scriban (`Moongate.Email`).\n- `IEmailSender`: transport abstraction with SMTP implementation (`SmtpEmailSender`).\n- `NoOpEmailSender`: selected automatically when email is disabled.\n- `websiteUrl`: global Scriban variable injected from `Http.WebsiteUrl`.\n\nDefault templates are loaded from:\n\n- `moongate_data/email/templates/registration_ok/*`\n- `moongate_data/email/templates/recover_password/*`\n\nRuntime directory mapping uses `DirectoryType.EmailTemplates`.\n\nMinimal config shape:\n\n```json\n{\n  \"email\": {\n    \"isEnabled\": false,\n    \"fromAddress\": \"noreply@localhost\",\n    \"fallbackLocale\": \"en\",\n    \"smtp\": {\n      \"host\": \"localhost\",\n      \"port\": 25,\n      \"useSsl\": false,\n      \"username\": null,\n      \"password\": null\n    }\n  }\n}\n```\n\n## Templates\n\nMoongate loads gameplay templates from `DirectoriesConfig[DirectoryType.Templates]`:\n\n- `templates/items/**/*.json` -\u003e loaded by `ItemTemplateLoader` into `IItemTemplateService`\n- `templates/mobiles/**/*.json` -\u003e loaded by `MobileTemplateLoader` into `IMobileTemplateService`\n\nTemplate values are data-driven and resolved at runtime using spec objects:\n\n- `HueSpec`: supports fixed values (`\"4375\"`, `\"0x1117\"`) and ranges (`\"hue(5:55)\"`)\n- `GoldValueSpec`: supports fixed values (`\"0\"`) and dice notation (`\"dice(1d8+8)\"`)\n\nExample item template:\n\n```json\n{\n  \"type\": \"item\",\n  \"id\": \"leather_backpack\",\n  \"name\": \"Leather Backpack\",\n  \"category\": \"Container\",\n  \"itemId\": \"0x0E76\",\n  \"hue\": \"hue(10:80)\",\n  \"goldValue\": \"dice(2d8+12)\",\n  \"lootType\": \"Regular\",\n  \"stackable\": false,\n  \"isMovable\": true\n}\n```\n\nExample startup item template:\n\n```json\n{\n  \"type\": \"item\",\n  \"id\": \"inner_torso\",\n  \"category\": \"Start Clothes\",\n  \"itemId\": \"0x1F7B\",\n  \"hue\": \"4375\",\n  \"goldValue\": \"dice(1d4+1)\",\n  \"weight\": 1\n}\n```\n\nExample mobile template:\n\n```json\n{\n  \"type\": \"mobile\",\n  \"id\": \"orione\",\n  \"name\": \"Orione\",\n  \"category\": \"animals\",\n  \"body\": \"0xC9\",\n  \"skinHue\": 779,\n  \"hairStyle\": 0,\n  \"brain\": \"orion\"\n}\n```\n\nResolution model:\n\n- JSON loading parses to typed specs (`HueSpec`, `GoldValueSpec`)\n- final random values are resolved when creating runtime entities (not at JSON load time)\n\n## Solution Structure\n\n- `src/Moongate.Server`: host/bootstrap, game loop, network orchestration, session/event services.\n- `src/Moongate.Network.Packets`: packet contracts, descriptors, registry, packet definitions.\n- `src/Moongate.Generators`: unified source generators for packets, handlers, metrics, script-module registry, and version metadata.\n- `src/Moongate.UO.Data`: UO domain data types and utility models.\n- `src/Moongate.Core`: shared low-level utilities.\n- `src/Moongate.Network`: TCP/network primitives.\n- `src/Moongate.Scripting`: Lua engine service, script modules, script loaders, and scripting helpers.\n- `src/Moongate.Server/Http`: embedded ASP.NET Core host service used by the server bootstrap.\n- `tests/Moongate.Tests`: unit tests.\n- `benchmarks/Moongate.Benchmarks`: BenchmarkDotNet performance suite.\n- `docs/`: documentation and project notes (plans, sprints, protocol notes, journal).\n\n## Source Generators (AOT)\n\nMoongate uses source generators to reduce runtime reflection/discovery work and improve Native AOT compatibility and startup performance.\n\nCurrent generator project:\n\n- `Moongate.Generators`\n  - Generates packet table/registry wiring and `PacketDefinition` constants from packet metadata.\n  - Generates bootstrap packet-listener registrations from `[RegisterPacketHandler(...)]`.\n  - Generates bootstrap game-event-listener subscriptions from `[RegisterGameEventListener]`.\n  - Generates bootstrap file-loader registrations from `[RegisterFileLoader(order)]`.\n  - Generates metric snapshot mappers from metric-decorated models.\n  - Generates script module registries from `[ScriptModule(...)]` in `Moongate.Scripting` and `Moongate.Server`.\n  - Generates `VersionUtils` metadata for server version/codename.\n\nWhy this helps for AOT:\n\n- Moves dynamic mapping logic from runtime to compile time.\n- Reduces dependency on reflection-based registration paths.\n- Improves deterministic startup behavior.\n\n## Event And Packet Separation\n\nMoongate uses a strict separation between inbound protocol parsing and outbound event projections:\n\n- `IPacketListener` handles inbound packets only (`Client -\u003e Server`) and applies domain use-cases.\n- Domain services publish `IGameEvent` messages through `IGameEventBusService`.\n- Game event listeners are declared with `IGameEventListener\u003cTEvent\u003e` and auto-subscribed at bootstrap via `[RegisterGameEventListener]`.\n- `IOutboundEventListener\u003cTEvent\u003e` handles outbound side-effects from domain events (for example enqueueing packets).\n- `RegisterOutboundEventListener\u003cTEvent, TListener\u003e()` is the bootstrap helper to register outbound listeners as hosted services with priority.\n- `IOutgoingPacketQueue` and `IOutboundPacketSender` deliver outbound packets on the game-loop/network boundary.\n\n## Game Loop Scheduling\n\nThe server loop is timestamp-driven (monotonic `Stopwatch`) rather than fixed-sleep tick stepping:\n\n- `GameLoopService` computes current loop timestamp and calls `ITimerService.UpdateTicksDelta(...)`.\n- `TimerWheelService` accumulates elapsed milliseconds and advances only the required number of wheel ticks.\n- This keeps timer semantics stable while adapting to real runtime load.\n- Optional idle throttling (`Game.IdleCpuEnabled`, `Game.IdleSleepMilliseconds`) sleeps briefly when no work was processed.\n\n### Background Jobs And Main-Thread Dispatch\n\nMoongate provides `IBackgroundJobService` to run non-gameplay work in parallel and safely marshal results back to the game loop thread.\n\nUse it for:\n\n- file parsing/import tasks\n- image generation and offline processors\n- CPU/I/O work that does not directly mutate world state\n\nDo not mutate gameplay state directly inside background workers.  \nPost results back to game loop callbacks instead.\n\nExample:\n\n```csharp\npublic sealed class SeedImportService\n{\n    private readonly IBackgroundJobService _backgroundJobService;\n\n    public SeedImportService(IBackgroundJobService backgroundJobService)\n    {\n        _backgroundJobService = backgroundJobService;\n    }\n\n    public void ImportAsync()\n    {\n        _backgroundJobService.RunBackgroundAndPostResultAsync(\n            async () =\u003e await LoadSeedStatsAsync(),\n            result =\u003e\n            {\n                // This callback executes on game-loop thread.\n                ApplyStatsToRuntime(result);\n            },\n            ex =\u003e\n            {\n                // Also marshaled on game-loop thread.\n                Log.Error(ex, \"Seed import failed.\");\n            }\n        );\n    }\n}\n```\n\n## Requirements\n\n- .NET SDK 10.0.x\n\n## Server Startup Tutorial\n\nThis is the recommended first-time setup to run the server locally.\n\n1. Prepare directories:\n   - `MOONGATE_ROOT_DIRECTORY`: server root (config, save, logs, scripts, templates).\n   - `MOONGATE_UO_DIRECTORY`: Ultima Online client data directory.\n2. Export env vars:\n\n```bash\nexport MOONGATE_ROOT_DIRECTORY=\"$HOME/moongate\"\nexport MOONGATE_UO_DIRECTORY=\"/path/to/uo-client\"\n```\n\n3. Restore/build/test:\n\n```bash\ndotnet restore\ndotnet build\ndotnet test\n```\n\n4. Start server:\n\n```bash\ndotnet run --project src/Moongate.Server\n```\n\n5. First startup behavior:\n   - If `moongate.json` is missing, it is created in `MOONGATE_ROOT_DIRECTORY`.\n   - Asset/data files are copied only when missing.\n   - If no accounts exist, a default admin is created.\n\n6. Optional admin credentials override:\n\n```bash\nexport MOONGATE_ADMIN_USERNAME=\"admin\"\nexport MOONGATE_ADMIN_PASSWORD=\"change-me-now\"\n```\n\n7. Verify runtime:\n   - Game TCP server: port `2593`\n   - HTTP endpoints (default): `http://localhost:8088/`, `http://localhost:8088/health`, `http://localhost:8088/metrics`, `http://localhost:8088/scalar`\n   - Logs: `MOONGATE_ROOT_DIRECTORY/logs`\n\n## Environment Configuration\n\nMoongate now supports full configuration override through environment variables.\n\n- Prefix: `MOONGATE_`\n- Nested properties: use `__` (double underscore)\n- Precedence: `MOONGATE_*` env vars override `moongate.json`\n\nExample:\n\n- `MOONGATE_HTTP__PORT=8088`\n- `MOONGATE_HTTP__JWT__ISSUER=moongate-http`\n- `MOONGATE_SPATIAL__SECTOR_ENTER_SYNC_RADIUS=3`\n\nSupported config env variables:\n\n- Core:\n  - `MOONGATE_ROOT_DIRECTORY`\n  - `MOONGATE_UO_DIRECTORY`\n  - `MOONGATE_LOG_LEVEL`\n  - `MOONGATE_LOG_PACKET_DATA`\n  - `MOONGATE_IS_DEVELOPER_MODE`\n- HTTP:\n  - `MOONGATE_HTTP__IS_ENABLED`\n  - `MOONGATE_HTTP__PORT`\n  - `MOONGATE_HTTP__WEBSITE_URL`\n  - `MOONGATE_HTTP__IS_OPEN_API_ENABLED`\n  - `MOONGATE_HTTP__JWT__IS_ENABLED`\n  - `MOONGATE_HTTP__JWT__SIGNING_KEY`\n  - `MOONGATE_HTTP__JWT__ISSUER`\n  - `MOONGATE_HTTP__JWT__AUDIENCE`\n  - `MOONGATE_HTTP__JWT__EXPIRATION_MINUTES`\n- Game:\n  - `MOONGATE_GAME__SHARD_NAME`\n  - `MOONGATE_GAME__TIMER_TICK_MILLISECONDS`\n  - `MOONGATE_GAME__TIMER_WHEEL_SIZE`\n  - `MOONGATE_GAME__IDLE_CPU_ENABLED`\n  - `MOONGATE_GAME__IDLE_SLEEP_MILLISECONDS`\n- Metrics:\n  - `MOONGATE_METRICS__ENABLED`\n  - `MOONGATE_METRICS__INTERVAL_MILLISECONDS`\n  - `MOONGATE_METRICS__LOG_ENABLED`\n  - `MOONGATE_METRICS__LOG_TO_CONSOLE`\n  - `MOONGATE_METRICS__LOG_LEVEL`\n- Persistence:\n  - `MOONGATE_PERSISTENCE__SAVE_INTERVAL_SECONDS`\n- Spatial:\n  - `MOONGATE_SPATIAL__LAZY_SECTOR_ITEM_LOAD_ENABLED`\n  - `MOONGATE_SPATIAL__SECTOR_WARMUP_RADIUS`\n  - `MOONGATE_SPATIAL__SECTOR_ENTER_SYNC_RADIUS`\n  - `MOONGATE_SPATIAL__LAZY_SECTOR_ENTITY_LOAD_RADIUS`\n  - `MOONGATE_SPATIAL__SECTOR_UPDATE_BROADCAST_RADIUS`\n  - `MOONGATE_SPATIAL__LIGHT_WORLD_START_UTC`\n  - `MOONGATE_SPATIAL__LIGHT_SECONDS_PER_UO_MINUTE`\n- Scripting:\n  - `MOONGATE_SCRIPTING__ENABLE_FILE_WATCHER`\n- Email:\n  - `MOONGATE_EMAIL__IS_ENABLED`\n  - `MOONGATE_EMAIL__FROM_ADDRESS`\n  - `MOONGATE_EMAIL__FALLBACK_LOCALE`\n  - `MOONGATE_EMAIL__SMTP__HOST`\n  - `MOONGATE_EMAIL__SMTP__PORT`\n  - `MOONGATE_EMAIL__SMTP__USE_SSL`\n  - `MOONGATE_EMAIL__SMTP__USERNAME`\n  - `MOONGATE_EMAIL__SMTP__PASSWORD`\n\nAdditional runtime env variables (not part of `MoongateConfig`):\n\n- `MOONGATE_ADMIN_USERNAME`\n- `MOONGATE_ADMIN_PASSWORD`\n- `MOONGATE_UI_DIST`\n- `MOONGATE_HTTP_JWT_SIGNING_KEY` (legacy explicit fallback; `MOONGATE_HTTP__JWT__SIGNING_KEY` is preferred)\n\n### Docker Compose Example\n\n```yaml\nservices:\n  moongate:\n    image: tgiachi/moongate:latest\n    environment:\n      MOONGATE_ROOT_DIRECTORY: /data/moongate\n      MOONGATE_UO_DIRECTORY: /data/uo\n      MOONGATE_HTTP__PORT: \"8088\"\n      MOONGATE_HTTP__IS_OPEN_API_ENABLED: \"true\"\n      MOONGATE_HTTP__JWT__SIGNING_KEY: \"change-me\"\n      MOONGATE_SPATIAL__SECTOR_ENTER_SYNC_RADIUS: \"3\"\n      MOONGATE_SPATIAL__SECTOR_UPDATE_BROADCAST_RADIUS: \"3\"\n      MOONGATE_SPATIAL__LIGHT_WORLD_START_UTC: \"1997-09-01T00:00:00Z\"\n      MOONGATE_SPATIAL__LIGHT_SECONDS_PER_UO_MINUTE: \"5\"\n      MOONGATE_PERSISTENCE__SAVE_INTERVAL_SECONDS: \"60\"\n      MOONGATE_EMAIL__IS_ENABLED: \"true\"\n      MOONGATE_EMAIL__SMTP__HOST: \"smtp.example.com\"\n      MOONGATE_EMAIL__SMTP__PORT: \"587\"\n      MOONGATE_EMAIL__SMTP__USE_SSL: \"true\"\n      MOONGATE_EMAIL__SMTP__USERNAME: \"smtp-user\"\n      MOONGATE_EMAIL__SMTP__PASSWORD: \"smtp-pass\"\n    volumes:\n      - ./moongate_data:/data/moongate\n      - ./uo:/data/uo:ro\n    ports:\n      - \"2593:2593\"\n      - \"8088:8088\"\n```\n\n## Quick Start\n\n```bash\ndotnet restore\ndotnet build\ndotnet test\ndotnet run --project src/Moongate.Server\n```\n\nBy default, the server starts with packet data logging enabled in `Program.cs`.\n\nConsole logging:\n\n- Custom Serilog console sink with output template compatible formatting.\n- Level-based colored output in terminal (Spectre.Console).\n- Placeholder values (message properties) highlighted with dedicated styling.\n- Fixed bottom prompt row (`moongate\u003e`) when running in an interactive terminal.\n\nHTTP service defaults:\n\n- `Http.IsEnabled = true`\n- `Http.Port = 8088`\n- `Http.WebsiteUrl = \"http://localhost\"`\n- `Http.IsOpenApiEnabled = true`\n- Base endpoint: `/`\n- Health endpoint: `/health`\n- OpenAPI JSON: `/openapi/v1.json`\n- Scalar UI: `/scalar`\n- Users API:\n  - `GET /api/users`\n  - `GET /api/users/{accountId}`\n  - `POST /api/users`\n  - `PUT /api/users/{accountId}`\n  - `DELETE /api/users/{accountId}`\n\n## Command System\n\nCommands now use a hybrid model:\n\n- **Primary path (C# built-ins)**: `ICommandExecutor` + `[RegisterConsoleCommand(...)]`\n  - Discovered and registered at compile-time by `ConsoleCommandRegistrationGenerator`\n  - Executors are registered as DryIoc singletons\n- **Secondary path (dynamic/Lua/future)**: manual `ICommandSystemService.RegisterCommand(...)`\n  - Kept intentionally for runtime registration scenarios\n\nAuthorization behavior:\n\n- Console source is always evaluated as `AccountType.Administrator`.\n- In-game source is evaluated using `GameSession.AccountType` (set during login).\n- If source is valid but role is too low, command execution is rejected with warning output.\n\nExample C# command registration (source-generated):\n\n```csharp\nusing Moongate.Server.Attributes;\nusing Moongate.Server.Data.Internal.Commands;\nusing Moongate.Server.Interfaces.Services.Console;\nusing Moongate.Server.Types.Commands;\nusing Moongate.UO.Data.Types;\n\n[RegisterConsoleCommand(\n    \"whoami|me\",\n    \"Shows basic identity information.\",\n    CommandSourceType.Console | CommandSourceType.InGame,\n    AccountType.Regular\n)]\npublic sealed class WhoAmICommand : ICommandExecutor\n{\n    public Task ExecuteCommandAsync(CommandSystemContext context)\n    {\n        context.Print(\"You are connected.\");\n        return Task.CompletedTask;\n    }\n}\n```\n\nExample dynamic/manual registration (runtime, e.g. Lua bridge):\n\n```csharp\ncommandSystemService.RegisterCommand(\n    \"lua_ping\",\n    context =\u003e\n    {\n        context.Print(\"pong\");\n        return Task.CompletedTask;\n    },\n    source: CommandSourceType.Console | CommandSourceType.InGame,\n    minimumAccountType: AccountType.Regular\n);\n```\n\nUsage:\n\n- Console: type command directly, for example `help`.\n- In-game: prefix with `.` in Unicode chat, for example `.help`.\n\nBuilt-in commands:\n\n- `help|?` -\u003e Console + InGame, `Regular`\n- `lock|*` -\u003e Console only, `Administrator`\n- `exit|shutdown` -\u003e Console only, `Administrator`\n- `add_user` -\u003e Console + InGame, `Administrator`\n- `send_target` -\u003e InGame only, `Regular`\n- `orion` -\u003e InGame only, `Regular` (opens target cursor and spawns Orion on selected location)\n- `teleport|tp` -\u003e InGame only, `GameMaster` (usage: `.teleport \u003cmapId\u003e \u003cx\u003e \u003cy\u003e \u003cz\u003e`)\n- `add_item_backpack|.add_item_backpack` -\u003e InGame only, `GameMaster` (usage: `.add_item_backpack \u003ctemplateId\u003e`)\n\n## Scripting\n\nMoongate includes a Lua scripting subsystem in `src/Moongate.Scripting`, based on MoonSharp.\n\n- `LuaScriptEngineService` handles script execution, callbacks, constants, and function invocation.\n- Script modules are exposed with attributes (`[ScriptModule]`, `[ScriptFunction]`).\n- Script module registration is compile-time generated (`ScriptModuleRegistry`) and invoked from bootstrap.\n- `LuaScriptLoader` resolves scripts from configured script directories.\n- `.luarc` metadata generation is included to improve editor tooling.\n\nCurrent automated coverage includes:\n\n- `LuaScriptLoader` file resolution and load behavior.\n- `LuaScriptEngineService` constants, callbacks, module calls, error path, and naming conversions.\n- `ScriptResultBuilder` success/error contract behavior.\n\nExample script callback (for example in `\u003croot\u003e/scripts/init.lua`):\n\n```lua\nfunction on_player_connected(p)\n log.info(\"Toh! un player s'e' connesso\")\nend\n```\n\n### NPC Brain Example (`brain_loop` + `on_event`)\n\nMobile template:\n\n```json\n{\n  \"type\": \"mobile\",\n  \"id\": \"orc_warrior\",\n  \"name\": \"an orc warrior\",\n  \"body\": \"0x11\",\n  \"brain\": \"orc_warrior\"\n}\n```\n\nLua script (`\u003croot\u003e/scripts/ai/orc_warrior.lua`):\n\n```lua\nfunction brain_loop(npc_id)\n  while true do\n    -- tactical tick sleep in milliseconds\n    coroutine.yield(250)\n  end\nend\n\nfunction on_event(event_type, from_serial, event_obj)\n  if event_type ~= \"speech_heard\" or event_obj == nil then\n    return\n  end\n\n  local listener_npc_id = event_obj.listener_npc_id\n  local text = event_obj.text\n  if listener_npc_id == nil or text == nil then\n    return\n  end\n\n  if string.find(string.lower(text), \"hello\", 1, true) then\n    log.info(\"NPC \" .. tostring(listener_npc_id) .. \" heard hello from \" .. tostring(from_serial))\n  end\nend\n```\n\nNotes:\n\n- `brain` in mobile templates is treated as a brain id.\n- Scripts are loaded from `moongate_data/scripts/**` (usually via `require(...)` in `init.lua`).\n- `brain_loop` is resumed by the runner and can control next wake time via `coroutine.yield(ms)`.\n- `on_event` is invoked with `(eventType, fromSerial, eventObject)`.\n- Current event type emitted by the brain runner: `speech_heard`.\n- `eventObject` contains: `listener_npc_id`, `speaker_id`, `text`, `speech_type`, `map_id`, and `location` (`x`, `y`, `z`).\n\n### Visual Effects From Lua\n\nMoongate now exposes visual effect helpers both on mobile proxies and as a global module:\n\n```lua\nlocal npc = mobile.get(0x00000030)\nif npc then\n  npc:SetEffect(0x3728, 10, 10, 0, 0, 2023)\nend\n\n-- broadcast location effect\neffect.send(1, 3613, 2585, 0, 0x3728, 10, 10, 0, 0, 2023)\n\n-- single target effect\neffect.send_to_player(0x00000022, 3613, 2585, 0, 0x3728, 10, 10, 0, 0, 5023)\n```\n\nRelated runtime events:\n\n- `MobilePlayEffectEvent` (broadcast in range)\n- `PlayEffectToPlayerEvent` (single session via character id)\n\n### Item `ScriptId` Dispatch\n\nItems can define `scriptId` in templates and runtime entities (`UOItemEntity.ScriptId`).\n`IItemScriptDispatcher` resolves `scriptId` as a Lua table and invokes hook functions on that table.\n\nDispatch convention:\n\n- If `scriptId` is set and not `none`: table name is normalized `scriptId` (non-alphanumeric -\u003e `_`, lowercase)\n- If `scriptId == \"none\"`: fallback table resolution from item name\n- First candidate: `\u003cnormalized_item_name\u003e`\n- Second candidate: `items_\u003cnormalized_item_name\u003e`\n- Hook names:\n- `single_click` -\u003e `on_click`\n- `double_click` -\u003e `on_double_click`\n\nGM Lua command examples shipped today:\n\n- `moongate_data/scripts/commands/gm/eclipse.lua` -\u003e `.eclipse`\n- `moongate_data/scripts/commands/gm/set_world_light.lua` -\u003e `.set_world_light \u003c0-255\u003e`\n- `moongate_data/scripts/commands/gm/teleports.lua` -\u003e `.teleports`\n\nExample:\n\n- `scriptId = \"items.healing-potion\"`\n- Lua table resolved: `items_healing_potion`\n- On single click dispatcher tries: `items_healing_potion.on_click` (and aliases)\n\nExample template:\n\n```json\n{\n  \"type\": \"item\",\n  \"id\": \"healing_potion\",\n  \"name\": \"a healing potion\",\n  \"itemId\": \"0x0F0C\",\n  \"scriptId\": \"items.healing_potion\"\n}\n```\n\nExample Lua:\n\n```lua\nitems_healing_potion = {\n  on_click = function(ctx)\n    log.info(\"Potion clicked, serial=\" .. tostring(ctx.item.serial))\n  end,\n  on_double_click = function(ctx)\n    log.info(\"Potion double clicked by mobile=\" .. tostring(ctx.mobile_id))\n  end\n}\n```\n\nFallback example (`scriptId = \"none\"` and item name `Brick`):\n\n```lua\nbrick = {\n  on_double_click = function(ctx)\n    log.info(\"Brick double-click from session \" .. tostring(ctx.session_id))\n  end\n}\n```\n\n`ctx` payload keys:\n\n- `hook`\n- `session_id`\n- `mobile_id`\n- `metadata`\n- `item`:\n- `serial`, `script_id`, `name`, `map_id`, `item_id`, `amount`, `hue`, `location.{x,y,z}`\n\n### Lua Gump Example\n\nMoongate now supports two complementary gump flows:\n\n- file-based layout table (recommended) with `gump.send_layout(...)`\n- runtime fluent builder with `gump.create()` / `gump.send(...)`\n\nFile-based layout conventions:\n\n- store gump files in `moongate_data/scripts/gumps/**.lua`\n- each file returns a table with `ui` and optional `handlers`\n- button click wiring is declarative: `onclick = \"handler_name\"`\n- optional `ctx` can be passed to `gump.send_layout(...)` for text placeholders (`$ctx.name`, `$ctx.level`, ...)\n\nExample file (`moongate_data/scripts/gumps/test_shop.lua`):\n\n```lua\nreturn {\n  ui = {\n    { type = \"page\", index = 0 },\n    { type = \"background\", x = 0, y = 0, gump_id = 9200, width = 320, height = 180 },\n    { type = \"label\", x = 20, y = 20, hue = 1152, text = \"Hello $ctx.name\" },\n    { type = \"button\", id = 1, x = 20, y = 130, normal_id = 4005, pressed_id = 4007, onclick = \"open_next\" }\n  },\n  handlers = {\n    open_next = function(cb_ctx)\n      log.info(\"Button clicked: \" .. tostring(cb_ctx.button_id))\n    end\n  }\n}\n```\n\nUsage:\n\n```lua\nlocal layout = require(\"gumps/test_shop\")\nlocal ui_ctx = { name = \"Orion\", level = 42 }\ngump.send_layout(session_id, layout, character_id, 0xB300, 120, 80, ui_ctx)\n```\n\nRuntime builder mode remains available for dynamic/UI-generated-at-runtime scenarios.\n\n## Scripts\n\nRepository helper scripts in `scripts/`:\n\n- `scripts/build_image.sh`: builds the Docker image using `docker buildx`, with options for tag, platform, push, and no-cache.\n- `scripts/run_aot.sh`: publishes and runs the server with NativeAOT settings for local AOT verification.\n- `scripts/run_benchmarks.sh`: runs BenchmarkDotNet benchmarks (`markdown` + `csv` exporters).\n- `scripts/run_benchmarks_compare.sh`: runs side-by-side `JIT vs NativeAOT` micro-benchmark comparison and writes `BenchmarkDotNet.Artifacts/results/aot-vs-jit.md`.\n- `scripts/run_benchmarks_lua.sh`: runs Lua script engine benchmarks only (JIT, MoonSharp is NativeAOT-incompatible). Accepts extra BenchmarkDotNet args.\n\n## Benchmarks\n\nRun locally:\n\n```bash\n./scripts/run_benchmarks.sh --filter '*'\n```\n\nLatest local snapshot (`2026-02-23`, `BenchmarkDotNet 0.14.0`, macOS `Darwin 25.3.0`, Apple `M4 Max`, `.NET 10.0.3`):\n\n| Benchmark | Mean | Allocated |\n|---|---:|---:|\n| `PacketParsingBenchmark.ParseLoginSeedPacket` | `94.82 ns` | `664 B` |\n| `PacketSerializationBenchmark.WriteServerListPacket` | `64.19 ns` | `128 B` |\n| `PacketStreamParsingBenchmark.ParseMixedPacketStreamInChunks` | `24.25 us` | `56 KB` |\n| `PacketDispatchBenchmark.DispatchToThreeListeners` | `68.21 ns` | `296 B` |\n| `PacketDispatchBenchmark.DispatchWithoutListeners` | `8.99 ns` | `64 B` |\n| `NetworkCompressionBenchmark.Compress256Bytes` | `220.76 ns` | `-` |\n| `NetworkCompressionBenchmark.CompressAndDecompress1024Bytes` | `60.03 us` | `48.10 KB` |\n| `NetworkCompressionBenchmark.CompressionMiddlewareProcessSend1024Bytes` | `908.72 ns` | `1.48 KB` |\n| `QueueThroughputBenchmark.OutgoingQueueEnqueueThenDrain` | `24.309 us` | `-` |\n| `QueueThroughputBenchmark.MessageBusPublishThenDrain` | `9.725 us` | `-` |\n| `TimerWheelBenchmark.UpdateTicksDelta` | `2.893 us` | `4.05 KB` |\n\n### Gameplay Hot-Path Benchmarks\n\nRun only the new gameplay-focused suites:\n\n```bash\ndotnet run -c Release --project benchmarks/Moongate.Benchmarks/Moongate.Benchmarks.csproj -- \\\n  --filter '*SpatialWorldServiceBenchmark*' '*ItemServiceBenchmark*' '*PacketGameplayHotPathBenchmark*'\n```\n\nLatest quick snapshot (`2026-03-02`, `BenchmarkDotNet 0.15.8`, macOS `Darwin 25.3.0`, Apple `M4 Max`, `.NET 10.0.3`, quick config `Launch=1/Warmup=1/Iteration=1`):\n\n| Benchmark | Mean | Allocated |\n|---|---:|---:|\n| `SpatialWorldServiceBenchmark.AddOrUpdateMobiles (500)` | `75.939 us` | `74.56 KB` |\n| `SpatialWorldServiceBenchmark.MoveMobilesAcrossSectors (500)` | `27.548 us` | `117.53 KB` |\n| `SpatialWorldServiceBenchmark.GetPlayersInHotSector (500)` | `1.769 us` | `6.16 KB` |\n| `SpatialWorldServiceBenchmark.AddOrUpdateMobiles (2000)` | `325.353 us` | `297.27 KB` |\n| `SpatialWorldServiceBenchmark.MoveMobilesAcrossSectors (2000)` | `105.423 us` | `469.15 KB` |\n| `SpatialWorldServiceBenchmark.GetPlayersInHotSector (2000)` | `1.745 us` | `6.16 KB` |\n| `ItemServiceBenchmark.MoveItemBetweenContainers` | `359.772 ns` | `1.85 KB` |\n| `ItemServiceBenchmark.DropItemToGroundFromContainer` | `489.566 ns` | `2.25 KB` |\n| `PacketGameplayHotPathBenchmark.ParseMoveRequestPacket` | `8.930 ns` | `32 B` |\n| `PacketGameplayHotPathBenchmark.ParsePickUpItemPacket` | `8.620 ns` | `32 B` |\n| `PacketGameplayHotPathBenchmark.ParseDropItemPacket` | `11.192 ns` | `48 B` |\n| `PacketGameplayHotPathBenchmark.ParseDropWearItemPacket` | `8.955 ns` | `32 B` |\n| `PacketGameplayHotPathBenchmark.ParseMixedGameplayPacketBurst` | `10.956 ns` | `36 B` |\n| `PacketGameplayHotPathBenchmark.WriteObjectInformationPacket` | `63.047 ns` | `-` |\n| `PacketGameplayHotPathBenchmark.WriteDraggingOfItemPacket` | `51.664 ns` | `-` |\n\nNotes:\n\n- This snapshot is intended for fast regression checks, not for publication-grade comparisons.\n- Use default/full BenchmarkDotNet settings for release notes and long-term trend baselines.\n\n### Lua Script Engine\n\nRun locally:\n\n```bash\n./scripts/run_benchmarks_lua.sh\n```\n\n\u003e Note: MoonSharp relies on reflection and dynamic code generation — NativeAOT is not supported for this suite.\n\nLatest local snapshot (`2026-02-25`, `BenchmarkDotNet 0.15.8`, macOS `Darwin 25.3.0`, Apple `M4 Max`, `.NET 10.0`):\n\n| Benchmark | Mean | Allocated |\n|---|---:|---:|\n| `LuaScriptEngineBenchmark.ExecuteSimpleScriptCached` | `328.87 ns` | `800 B` |\n| `LuaScriptEngineBenchmark.ExecuteLoopScriptCached` | `5.68 us` | `19.67 KB` |\n| `LuaScriptEngineBenchmark.ExecuteSimpleScriptUncached` | `6.28 us` | `6.12 KB` |\n| `LuaScriptEngineBenchmark.CallFunctionNoArgs` | `49.22 ns` | `256 B` |\n| `LuaScriptEngineBenchmark.CallFunctionWithArgs` | `135.40 ns` | `864 B` |\n\nGenerated reports are stored in:\n\n- `BenchmarkDotNet.Artifacts/results/*.md`\n- `BenchmarkDotNet.Artifacts/results/*.csv`\n\n### AOT vs JIT\n\nRun side-by-side comparison:\n\n```bash\n./scripts/run_benchmarks_compare.sh\n```\n\nLatest comparison snapshot (`2026-02-23`, `net10.0`, Apple `M4 Max`, `osx-arm64`):\n\n| Benchmark | JIT Mean | AOT Mean | Speedup (JIT/AOT) |\n|---|---:|---:|---:|\n| `Compress256Bytes` | `934.48 ns` | `319.04 ns` | `2.93x` |\n| `CompressAndDecompress1024Bytes` | `59.60 us` | `102.20 us` | `0.58x` |\n| `CompressionMiddlewareProcessSend1024Bytes` | `974.86 ns` | `1.34 us` | `0.73x` |\n| `ParseLoginSeedPacket` | `360.97 ns` | `71.66 ns` | `5.04x` |\n| `ParseMixedPacketStreamInChunks` | `26.10 us` | `37.71 us` | `0.69x` |\n| `WriteServerListPacket` | `585.93 ns` | `98.31 ns` | `5.96x` |\n\nDetailed report:\n\n- `BenchmarkDotNet.Artifacts/results/aot-vs-jit.md`\n\n## Stress Test (Socket UO, Black-Box)\n\nUse the dedicated stress runner to validate server stability with real UO socket clients.\n\nScenario target (default):\n\n- `100` concurrent clients\n- `300s` duration\n- account bootstrap via HTTP users API\n- login + enter world + continuous movement loop\n- SLO checks:\n  - login success rate `\u003e= 99%`\n  - unexpected disconnects `= 0`\n  - movement ACK p95 `\u003c 200ms`\n\nRun:\n\n```bash\ndotnet run --project tools/Moongate.Stress -- \\\n  --host 127.0.0.1 --port 2593 \\\n  --http http://localhost:8088 \\\n  --clients 100 --duration 300 --ramp-up-per-second 10\n```\n\nWhen JWT protection is enabled on `/api/users`, provide admin credentials:\n\n```bash\ndotnet run --project tools/Moongate.Stress -- \\\n  --admin-username admin --admin-password your_password\n```\n\nOutput:\n\n- console summary with pass/fail and SLO violations\n- JSON report at `artifacts/stress/latest.json`\n\n## Docker\n\nBuild the image:\n\n```bash\n./scripts/build_image.sh -t moongate-server:local\n```\n\nRun the container:\n\n```bash\ndocker run --rm -it \\\n  -p 2593:2593 \\\n  -p 8088:8088 \\\n  -v /path/host/moongate-root:/app \\\n  -v /path/host/uo-client:/uo:ro \\\n  --name moongate \\\n  moongate-server:local\n```\n\nThe Docker image publishes a NativeAOT binary and runs it on Alpine (`linux-musl` runtime).\nIt also builds the frontend in `ui/` and serves it from `/` via the HTTP service.\nContainer defaults:\n\n- `MOONGATE_ROOT_DIRECTORY=/app`\n- `MOONGATE_UO_DIRECTORY=/uo`\n- `MOONGATE_UI_DIST=/opt/moongate/ui/dist`\n\n`/path/host/uo-client` must contain required UO client files (e.g. `client.exe`).\n\nConsole behavior in Docker:\n\n- Run with `-it` to enable the interactive prompt UI (`moongate\u003e`).\n- Without TTY (`-it` omitted), logs still work but prompt interaction is disabled.\n\n## Docker Monitoring Stack\n\nThe repository includes a complete monitoring stack under `stack/`:\n\n- Moongate server container\n- Prometheus scraping `http://moongate:8088/metrics`\n- Grafana with pre-provisioned datasource and dashboard\n\nQuick start:\n\n```bash\ncd stack\ndocker compose up -d --build\n```\n\nUseful endpoints:\n\n- Grafana: `http://localhost:3000`\n- Prometheus: `http://localhost:9090`\n- Moongate metrics: `http://localhost:8088/metrics`\n\nFor full setup details, volumes, troubleshooting, and dashboard notes, see `stack/README.md`.\n\n## Documentation\n\nProject documentation is in `docs/`.\nPublished documentation is available at:\n\n- https://moongate-community.github.io/moongatev2/\n\n- Docs home: `docs/Home.md`\n- Development plan: `docs/plans/moongate-v2-development-plan.md`\n- Current status snapshot: `docs/plans/status-2026-02-19.md`\n- Sprint tracking: `docs/sprints/sprint-001.md`\n- Sprint closeout: `docs/sprints/sprint-001-closeout-2026-02-18.md`\n- Protocol notes index: `docs/protocol/README.md`\n\n## Development Notes\n\n- Shared build/analyzer/version settings are centralized in `Directory.Build.props`.\n- Current global version baseline: `0.17.0`.\n- CI validates build/tests/coverage/quality/security; release and Docker image publishing run through dedicated workflows.\n\n## Contributing\n\nWe welcome contributions. Please fork the repository and submit pull requests with your changes.\nMake sure code follows the project coding standards and includes appropriate tests.\n\n## License\n\nThis project is licensed under the GNU General Public License v3.0 (GPL-3.0).\nSee `LICENSE` for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoongate-community%2Fmoongatev2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmoongate-community%2Fmoongatev2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmoongate-community%2Fmoongatev2/lists"}