Files
AdamuSw/docs/superpowers/plans/2026-07-19-new-maps-arkania-pilot.md
Acentech Dev a61890ef6e docs: implementation plan for Arkania map pilot
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>
2026-07-19 13:31:34 +03:00

21 KiB
Raw Blame History

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 (<None Remove> + <EmbeddedResource Include>). 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 <None Remove> and an <EmbeddedResource Include>)

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):

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 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):

// <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) 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:

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 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:

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 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.