7 tasks: terrain resource (Terrain84.att, off-by-one), map class + registration, update-plugin with spawn gate + /move warp, docker build/verify, deploy + panel verify, client assets, client verify. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21 KiB
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:
AdamuSwatD:\OpenMU\MU Client_Mobile 1.04d - Season 6E3\AdamuSw, work on branchfeature/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 theName/class name. - Terrain resource naming is OFF-BY-ONE: OpenMU map number
Nloads embedded resourceResources/Terrain{N+1}.att. For Arkania (83) that isTerrain84.att, whose CONTENT is the pack'sTerrain83.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 (
<None Remove>+<EmbeddedResource Include>). New.attMUST be added to both lists. - Update-plugins are applied to existing DBs ONLY via AdminPanel → Updates (
DataUpdateService); never callSaveChangesinside 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 theAdamuSwroot. Localdotnet buildis 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 packServer Side/Data/Terrain/Terrain83.att) - Modify:
AdamuSw/src/Persistence/Initialization/MUnique.OpenMU.Persistence.Initialization.csproj(two lines: a<None Remove>and an<EmbeddedResource Include>)
Interfaces:
-
Produces: embedded manifest resource
MUnique.OpenMU.Persistence.Initialization.Resources.Terrain84.att, consumed byTerrainUpdateHelper.UpdateTerrainFromResourcesfor map number 83. -
Step 1: Copy the terrain file (pack Terrain83.att → resource Terrain84.att)
Run (Git 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:
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 <None Remove=...> group (near <None Remove="Resources\Terrain1.att" />):
<None Remove="Resources\Terrain84.att" />
In the <EmbeddedResource Include=...> group (near <EmbeddedResource Include="Resources\Terrain1.att" />):
<EmbeddedResource Include="Resources\Terrain84.att" />
- Step 4: Verify both csproj lines are present
Run:
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 <None Remove ...Terrain84.att /> and one <EmbeddedResource Include ...Terrain84.att />.
- Step 5: Commit
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 oneyield return)
Interfaces:
-
Produces:
internal class Arkania : BaseMapInitializerwithinternal const byte Number = 83;andinternal const string Name = "Arkania";. Consumed byGameMapsInitializer(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):
// <copyright file="Arkania.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.Persistence.Initialization.VersionSeasonSix.Maps;
using MUnique.OpenMU.DataModel.Configuration;
/// <summary>
/// The initialization for the Arkania map (imported map, world 83).
/// Seeded empty; monster/NPC spawns and gates are added via the admin panel.
/// </summary>
internal class Arkania : BaseMapInitializer
{
/// <summary>
/// The Number of the Map.
/// </summary>
internal const byte Number = 83;
/// <summary>
/// The Name of the Map.
/// </summary>
internal const string Name = "Arkania";
/// <summary>
/// Initializes a new instance of the <see cref="Arkania"/> class.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="gameConfiguration">The game configuration.</param>
public Arkania(IContext context, GameConfiguration gameConfiguration)
: base(context, gameConfiguration)
{
}
/// <inheritdoc/>
protected override byte MapNumber => Number;
/// <inheritdoc/>
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:
yield return typeof(Arkania);
- Step 3: Verify class + registration compile-consistency (textual)
Run:
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
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) andArkania.Name("Arkania") from Task 2; theTerrain84.attresource from Task 1. -
Produces: update-plugin
AddArkaniaMapUpdateSeason6withVersion => 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:
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<best[0]:
best=(dist,x,y,a)
print("spawn X=%d Y=%d (attr=0x%02x, dist-from-center=%d)"%(best[1],best[2],best[3],best[0]))
PY
Expected: prints a concrete X/Y (e.g. spawn X=128 Y=130 ...). Record these as SPAWN_X / SPAWN_Y for Step 3.
- Step 2: Add the UpdateVersion enum value
In Updates/UpdateVersion.cs, after AddImperialWeapons050Season6 = 95, add:
/// <summary>
/// Adds the imported Arkania map (number 83).
/// </summary>
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:
// <copyright file="AddArkaniaMapUpdateSeason6.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
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;
/// <summary>
/// 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.
/// </summary>
[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.";
/// <inheritdoc />
public override UpdateVersion Version => UpdateVersion.AddArkaniaMapSeason6;
/// <inheritdoc />
public override string DataInitializationKey => VersionSeasonSix.DataInitialization.Id;
/// <inheritdoc />
public override string Name => PlugInName;
/// <inheritdoc />
public override string Description => PlugInDescription;
/// <inheritdoc />
public override bool IsMandatory => true;
/// <inheritdoc />
public override DateTime CreatedAt => new(2026, 07, 19, 12, 0, 0, DateTimeKind.Utc);
/// <inheritdoc />
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<ExitGate>();
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<WarpInfo>();
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:
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
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):
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:
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:
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:
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 theLocal\Engadditions.
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:
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:
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:
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 Arkaniaenters 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← packTerrain83.att). - No monster spawns in the seed — intentional (user adds via panel), matching the spec.
- Type consistency:
Arkania.Number(83) andArkania.Name("Arkania") defined in Task 2 are the exact symbols consumed in Task 3;UpdateVersion.AddArkaniaMapSeason6(=96) defined and consumed consistently.