diff --git a/docs/superpowers/plans/2026-07-19-new-maps-arkania-pilot.md b/docs/superpowers/plans/2026-07-19-new-maps-arkania-pilot.md new file mode 100644 index 0000000..72ff3fc --- /dev/null +++ b/docs/superpowers/plans/2026-07-19-new-maps-arkania-pilot.md @@ -0,0 +1,446 @@ +# New Map Import — Arkania Pilot Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Import one pilot map (Arkania, map number 83) end-to-end into OpenMU server, the PC MUnique client, and make it visible/editable in the web admin panel. + +**Architecture:** OpenMU-native map: a C# `BaseMapInitializer` subclass registers map number 83 into `GameConfiguration.Maps`; its terrain comes from an embedded `.att` resource; an update-plugin applies the same to an existing DB. The map is seeded EMPTY (no monsters/NPCs) plus one spawn gate and a `/move` warp for reachability. The web admin panel needs no code changes — the map appears automatically once in the DB. PC client gets the `World83`/`Object83` asset folders. + +**Tech Stack:** C# (.NET), OpenMU `Persistence.Initialization`, Docker build, MUnique MuMain PC client (Data folder assets). + +## Global Constraints + +- Server repo: `AdamuSw` at `D:\OpenMU\MU Client_Mobile 1.04d - Season 6E3\AdamuSw`, work on branch `feature/new-maps-import` (already created). +- Pilot map: **Arkania**, **map number 83** (client `World83`). Confirm the name against the pack screenshots before finalizing; if it turns out World83 is a different map, change only the `Name`/class name. +- **Terrain resource naming is OFF-BY-ONE:** OpenMU map number `N` loads embedded resource `Resources/Terrain{N+1}.att`. For Arkania (83) that is **`Terrain84.att`**, whose CONTENT is the pack's `Terrain83.att` (world-83 attributes). Verified against Aida (33→Terrain34.att), Vulcanus (63→Terrain64.att). +- Terrain resources are NOT globbed by the csproj — each is listed explicitly (`` + ``). New `.att` MUST be added to both lists. +- Update-plugins are applied to existing DBs ONLY via AdminPanel → Updates (`DataUpdateService`); never call `SaveChanges` inside a plugin. Fresh/zero DB auto-seeds via the map registration. +- Server build is via Docker: `docker build -f Startup/Dockerfile -t adamu-openmu:dev .` from the `AdamuSw` root. Local `dotnet build` is expected to fail at the source-generator pre-step (known, per the Imperial-items work) — use the Docker build as the compile gate. +- Pack source: `C:\Users\efpa\Downloads\Arkania Acheron Debenter Uruk Ferea\Arkania Acheron Debenter Uruk Ferea`. +- PC client Data root: `D:\AdaMu\AdaMu\Data`. + +--- + +### Task 1: Add the Arkania terrain resource + +**Files:** +- Create: `AdamuSw/src/Persistence/Initialization/Resources/Terrain84.att` (copied from pack `Server Side/Data/Terrain/Terrain83.att`) +- Modify: `AdamuSw/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj` (two lines: a `` and an ``) + +**Interfaces:** +- Produces: embedded manifest resource `MUnique.OpenMU.Persistence.Initialization.Resources.Terrain84.att`, consumed by `TerrainUpdateHelper.UpdateTerrainFromResources` for map number 83. + +- [ ] **Step 1: Copy the terrain file (pack Terrain83.att → resource Terrain84.att)** + +Run (Git Bash): +```bash +SRC="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Server Side/Data/Terrain/Terrain83.att" +DST="/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Resources/Terrain84.att" +cp -f "$SRC" "$DST" +stat -c '%s %n' "$DST" +``` +Expected: prints `65539 .../Terrain84.att` (matches OpenMU's other `.att` sizes). + +- [ ] **Step 2: Verify the header matches OpenMU's terrain format** + +Run: +```bash +xxd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Resources/Terrain84.att" | head -1 +xxd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Resources/Terrain1.att" | head -1 +``` +Expected: both start with `00ff ff04` (same 3-byte header family). If Terrain84.att differs structurally, STOP — the pack file may be a different format. + +- [ ] **Step 3: Register the resource in the csproj** + +In `MUnique.OpenMU.Persistence.Initialization.csproj`, add next to the existing `Terrain*.att` entries: + +In the `` group (near ``): +```xml + +``` +In the `` group (near ``): +```xml + +``` + +- [ ] **Step 4: Verify both csproj lines are present** + +Run: +```bash +grep -n "Terrain84.att" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj" +``` +Expected: exactly two lines — one `` and one ``. + +- [ ] **Step 5: Commit** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" +git add src/Persistence/Initialization/Resources/Terrain84.att src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj +git commit -m "feat(maps): add Arkania (map 83) terrain resource Terrain84.att" +``` + +--- + +### Task 2: Create the Arkania map initializer and register it + +**Files:** +- Create: `AdamuSw/src/Persistence/Initialization/VersionSeasonSix/Maps/Arkania.cs` +- Modify: `AdamuSw/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs` (add one `yield return`) + +**Interfaces:** +- Produces: `internal class Arkania : BaseMapInitializer` with `internal const byte Number = 83;` and `internal const string Name = "Arkania";`. Consumed by `GameMapsInitializer` (fresh seed) and the update-plugin (Task 3). + +- [ ] **Step 1: Create the map class** + +Create `Arkania.cs` (mirrors the `Aida.cs` pattern, but with no monster/NPC spawns — those are added later via the web panel): +```csharp +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; + +using MUnique.OpenMU.DataModel.Configuration; + +/// +/// The initialization for the Arkania map (imported map, world 83). +/// Seeded empty; monster/NPC spawns and gates are added via the admin panel. +/// +internal class Arkania : BaseMapInitializer +{ + /// + /// The Number of the Map. + /// + internal const byte Number = 83; + + /// + /// The Name of the Map. + /// + internal const string Name = "Arkania"; + + /// + /// Initializes a new instance of the class. + /// + /// The context. + /// The game configuration. + public Arkania(IContext context, GameConfiguration gameConfiguration) + : base(context, gameConfiguration) + { + } + + /// + protected override byte MapNumber => Number; + + /// + protected override string MapName => Name; +} +``` + +- [ ] **Step 2: Register the map in GameMapsInitializer** + +In `VersionSeasonSix/GameMapsInitializer.cs`, in `MapInitializerTypes`, add after the last existing `yield return typeof(...);` line: +```csharp + yield return typeof(Arkania); +``` + +- [ ] **Step 3: Verify class + registration compile-consistency (textual)** + +Run: +```bash +grep -n "class Arkania : BaseMapInitializer" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/VersionSeasonSix/Maps/Arkania.cs" +grep -n "typeof(Arkania)" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs" +``` +Expected: one match each. + +- [ ] **Step 4: Commit** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" +git add src/Persistence/Initialization/VersionSeasonSix/Maps/Arkania.cs src/Persistence/Initialization/VersionSeasonSix/GameMapsInitializer.cs +git commit -m "feat(maps): add Arkania map initializer (number 83) and register it" +``` + +--- + +### Task 3: Add spawn gate + `/move` warp, and the DB update-plugin + +**Files:** +- Modify: `AdamuSw/src/Persistence/Initialization/Updates/UpdateVersion.cs` (add enum value) +- Create: `AdamuSw/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs` + +**Interfaces:** +- Consumes: `Arkania.Number` (83) and `Arkania.Name` ("Arkania") from Task 2; the `Terrain84.att` resource from Task 1. +- Produces: update-plugin `AddArkaniaMapUpdateSeason6` with `Version => UpdateVersion.AddArkaniaMapSeason6`. + +- [ ] **Step 1: Compute a walkable spawn coordinate from the terrain** + +The spawn gate must sit on a walkable tile. Scan the pack `Terrain83.att` (3-byte header + 256×256 attribute bytes; a tile is walkable when its attribute byte has none of the blocking bits `0x04` NOMOVE / `0x08` NOGROUND set — i.e. value `0x00` or `0x01` safezone). Run: +```bash +python - <<'PY' +p="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Server Side/Data/Terrain/Terrain83.att" +d=open(p,'rb').read()[3:] # drop 3-byte header +# attribute[y*256 + x]; find a walkable tile nearest the center +best=None +for y in range(256): + for x in range(256): + a=d[y*256+x] + if a & 0x04 or a & 0x08: # NOMOVE or NOGROUND -> blocked + continue + dist=abs(x-128)+abs(y-128) + if best is None or dist + /// Adds the imported Arkania map (number 83). + /// + AddArkaniaMapSeason6 = 96, +``` + +- [ ] **Step 3: Create the update-plugin** + +Create `Updates/AddArkaniaMapUpdateSeason6.cs` (mirrors `AddImperialWeapons050UpdateSeason6.cs` for the boilerplate and `AddLorenMarketJuliaWarpPlugIn.cs` for the inline gate/warp). Replace `SPAWN_X`/`SPAWN_Y` with the values from Step 1: +```csharp +// +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace MUnique.OpenMU.Persistence.Initialization.Updates; + +using System.Runtime.InteropServices; +using MUnique.OpenMU.DataModel.Configuration; +using MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps; +using MUnique.OpenMU.PlugIns; + +/// +/// Adds the imported Arkania map (number 83): the map definition with its terrain, +/// a spawn gate on a walkable tile, and a /move warp entry so players can reach it. +/// Monsters and NPCs are added afterwards via the admin panel. +/// +[PlugIn] +[Display(Name = PlugInName, Description = PlugInDescription)] +[Guid("2C8B0A61-6B2E-4D7A-9E3C-7F1A4B2D9E01")] +public class AddArkaniaMapUpdateSeason6 : UpdatePlugInBase +{ + internal const string PlugInName = "Add Arkania map (83)"; + internal const string PlugInDescription = "Adds the imported Arkania map (number 83) with terrain, a spawn gate, and a /move warp. Monsters/NPCs are added via the admin panel."; + + /// + public override UpdateVersion Version => UpdateVersion.AddArkaniaMapSeason6; + + /// + public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id; + + /// + public override string Name => PlugInName; + + /// + public override string Description => PlugInDescription; + + /// + public override bool IsMandatory => true; + + /// + public override DateTime CreatedAt => new(2026, 07, 19, 12, 0, 0, DateTimeKind.Utc); + + /// + protected override ValueTask ApplyAsync(IContext context, GameConfiguration gameConfiguration) + { + // Idempotency guard: skip if Arkania (83) already exists. + if (gameConfiguration.Maps.Any(m => m.Number == Arkania.Number)) + { + return default; + } + + var initializer = new Arkania(context, gameConfiguration); + initializer.Initialize(); // creates map 83 + loads terrain from Terrain84.att + initializer.SetSafezoneMap(); + + var arkania = gameConfiguration.Maps.First(m => m.Number == Arkania.Number); + + // Spawn gate on a walkable tile (coordinates from the terrain scan). + var spawnGate = context.CreateNew(); + arkania.ExitGates.Add(spawnGate); + spawnGate.Map = arkania; + spawnGate.X1 = SPAWN_X; + spawnGate.Y1 = SPAWN_Y; + spawnGate.X2 = SPAWN_X; + spawnGate.Y2 = SPAWN_Y; + spawnGate.IsSpawnGate = true; + + // /move warp entry. + if (gameConfiguration.WarpList.All(w => w.Name != Arkania.Name)) + { + var warp = context.CreateNew(); + warp.Index = 83; + warp.Name = Arkania.Name; + warp.Costs = 5000; + warp.LevelRequirement = 10; + warp.Gate = spawnGate; + } + + return default; + } +} +``` + +- [ ] **Step 4: Verify enum + plugin references are consistent (textual)** + +Run: +```bash +grep -n "AddArkaniaMapSeason6 = 96" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Updates/UpdateVersion.cs" +grep -n "UpdateVersion.AddArkaniaMapSeason6" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs" +grep -cn "SPAWN_X\|SPAWN_Y" "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw/src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs" +``` +Expected: first two match; the third prints `0` (all `SPAWN_X`/`SPAWN_Y` placeholders were replaced with numbers). If it prints non-zero, replace the remaining placeholders. + +- [ ] **Step 5: Commit** + +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" +git add src/Persistence/Initialization/Updates/UpdateVersion.cs src/Persistence/Initialization/Updates/AddArkaniaMapUpdateSeason6.cs +git commit -m "feat(maps): add Arkania DB update-plugin with spawn gate and /move warp" +``` + +--- + +### Task 4: Build the server image and verify map 83 is in the configuration + +**Files:** none (build + verification only). + +- [ ] **Step 1: Docker build** + +Run (from AdamuSw root): +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" +docker build -f Startup/Dockerfile -t adamu-openmu:dev . 2>&1 | tail -20 +``` +Expected: `Successfully tagged adamu-openmu:dev` (or the buildkit equivalent). If compilation fails, fix the reported C# error and rebuild. + +- [ ] **Step 2: Verify the map symbol is compiled into the initialization DLL** + +Run: +```bash +docker run --rm --entrypoint sh adamu-openmu:dev -c "grep -a -c Arkania /app/MUnique.OpenMU.Persistence.Initialization.dll || true" +``` +Expected: a non-zero count (the `Arkania`/`AddArkaniaMap` strings are present in the built DLL). If `0`, the class was not compiled in — recheck Task 2/3. + +- [ ] **Step 3: Commit (no code; checkpoint only if any fixes were made)** + +Only if Steps 1-2 required code fixes: +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" +git commit -am "fix(maps): resolve Arkania build errors" +``` + +--- + +### Task 5: Deploy the server and apply the update on the existing DB + +**Files:** none (deploy + admin action). + +- [ ] **Step 1: Bring the server up with the new image** + +Run: +```bash +cd "/d/OpenMU/MU Client_Mobile 1.04d - Season 6E3/AdamuSw" +docker compose -f deploy-adamu/docker-compose.local.yml up -d +``` +Expected: containers start (game server + admin panel). Note: if the server points at the remote `65.109.224.204` DB instead of local, deploy the image to that host per the established deploy flow instead. + +- [ ] **Step 2: Apply the update via AdminPanel → Updates** + +Open the AdminPanel (`http://localhost:8081` locally, or the server's admin URL), go to the **Updates** page, and apply **"Add Arkania map (83)"**. (Fresh/zero DBs already have it from the seed and will show nothing to apply.) +Expected: the update applies without error and disappears from the pending list. + +- [ ] **Step 3: Verify the map exists in the panel** + +In the AdminPanel, open `/edit-config-grid/GameMapDefinition` (Config → Game Maps). +Expected: **Arkania** appears in the list with number 83. + +- [ ] **Step 4: Verify terrain renders in the visual editor** + +Open `/map-editor`, select **Arkania**. +Expected: the terrain image renders (not blank). Blank terrain ⇒ the `Terrain84.att` resource didn't load — recheck Task 1 (csproj embedding + off-by-one name). + +--- + +### Task 6: Copy the client assets to the PC client + +**Files:** +- Copy into `D:\AdaMu\AdaMu\Data`: `World83\`, `Object83\`, and the `Local\Eng` additions. + +**Interfaces:** +- Consumes: pack `Client Side/Data/{World83,Object83,Local/Eng}`. +- Produces: renderable client-side assets for world index 83. + +- [ ] **Step 1: Copy World83 and Object83** + +Run: +```bash +PACK="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Client Side/Data" +DST="/d/AdaMu/AdaMu/Data" +cp -rf "$PACK/World83" "$DST/World83" +cp -rf "$PACK/Object83" "$DST/Object83" +ls "$DST/World83/EncTerrain83.att" "$DST/Object83" >/dev/null && echo "World83+Object83 copied" +``` +Expected: prints `World83+Object83 copied`. + +- [ ] **Step 2: Copy the map-name image and minimap for world 83** + +Run: +```bash +PACK="/c/Users/efpa/Downloads/Arkania Acheron Debenter Uruk Ferea/Arkania Acheron Debenter Uruk Ferea/Client Side/Data/Local/Eng" +DST="/d/AdaMu/AdaMu/Data/Local/Eng" +[ -f "$PACK/ImgsMapName/Arkania.OZT" ] && cp -f "$PACK/ImgsMapName/Arkania.OZT" "$DST/ImgsMapName/" && echo "map-name image copied" +ls "$PACK/Minimap/" | grep -i "World83" | while read f; do cp -f "$PACK/Minimap/$f" "$DST/Minimap/"; echo "minimap $f copied"; done +``` +Expected: the Arkania name image copies; a `Minimap_World83_eng.bmd` copies if present (absence is non-fatal — minimap only). + +- [ ] **Step 3: Verify no case-mismatch on referenced object textures** + +The MUnique PC client on Windows is case-insensitive, so a spot check suffices. Run: +```bash +ls "/d/AdaMu/AdaMu/Data/World83" | head; echo "---"; ls "/d/AdaMu/AdaMu/Data/Object83" | wc -l +``` +Expected: World83 lists `EncTerrain83.*` + tiles; Object83 has a non-zero file count. + +_(No commit — `D:\AdaMu\AdaMu` is the deployed client folder, not a git repo.)_ + +--- + +### Task 7: Verify in the PC client + +**Files:** none (runtime verification). May require a `vendor/MuMain` change + rebuild if the engine caps the world index. + +- [ ] **Step 1: Launch the client and reach Arkania** + +Start `D:\AdaMu\AdaMu\main.exe`, log in to a character (server `65.109.224.204`), and issue `/move Arkania` in chat. +Expected: the character warps to Arkania; terrain and objects render; no `OpenTexture Failed` / crash; the character can walk. + +- [ ] **Step 2: If the map does not load (world-index unsupported by the engine)** + +If the client crashes or shows a blank/again-Lorencia world on entering 83, the MUnique engine may not handle world index 83. Check the terrain/world-loading path in `vendor/MuMain/src/source` for a hard world-index cap or a per-index table, add support for 83, and rebuild `Main.exe` (see the `pc-munique-client-build` memory: `vcvarsall x86` + vswhere on PATH + `cmake --build out/build/windows-x86-fresh --config Release`), then redeploy `main.exe` to `D:\AdaMu\AdaMu`. Re-run Step 1. + +- [ ] **Step 3: Final confirmation** + +Confirm all three surfaces: +- Server/panel: Arkania (83) listed, terrain renders in `/map-editor`. +- Client: `/move Arkania` enters a walkable, correctly-rendered map. +- Then the user adds monster/NPC spawns via the panel and confirms they appear live. + +Pilot complete. Follow-up (separate plan): replicate Tasks 1-7 for maps 84-87 (Acheron/Debenter/Uruk/Ferea) — each terrain resource is `Terrain{N+1}.att` from the pack's `Terrain{N}.att` — plus mobile-client asset import. + +## Self-Review notes + +- **Spec coverage:** terrain resource (Task 1), map class + registration (Task 2), update-plugin + warp/gate (Task 3), build/verify (Task 4), deploy/apply/panel-verify (Task 5), client assets (Task 6), client verify (Task 7). All spec sections covered. +- **Off-by-one terrain naming** is captured as a Global Constraint and applied in Task 1 (resource `Terrain84.att` ← pack `Terrain83.att`). +- **No monster spawns in the seed** — intentional (user adds via panel), matching the spec. +- **Type consistency:** `Arkania.Number` (83) and `Arkania.Name` ("Arkania") defined in Task 2 are the exact symbols consumed in Task 3; `UpdateVersion.AddArkaniaMapSeason6` (=96) defined and consumed consistently.