Arcane Sanctuary

Summoner Direction Analysis -- Disassembled from D2Common.dll (v1.13c)
The Summoner direction is rand() & 3.

There is no trick, no visual indicator, no tile pattern, and no relationship to any other map feature.
The direction is a single pseudorandom number derived from the map seed. It cannot be predicted in-game.

1. The Algorithm (from disassembly)

The Summoner direction is assigned in a single function called immediately after the maze is grown. It lives in D2Common.dll, in what the original source called Maze.cpp (the path ..\Source\D2Common\DRLG\Maze.cpp is embedded in the binary as an assert string).

Raw x86 disassembly (VA 0x6FD61940)

; ---- Post-MazeGrow Endpoint Assignment ----
; Called after the maze corridors are fully generated.
; Picks one of 4 directions for the Summoner platform.

push    ecx
mov     eax, [esi+0x1C4]         ; load PRNG seed (low 32 bits)
mov     ecx, 0x6AC690C5          ; LCG multiplier constant
mul     ecx                        ; edx:eax = seed_lo * multiplier
mov     ecx, [esi+0x1C8]         ; load PRNG seed (high 32 bits)
push    edi
xor     edi, edi
add     eax, ecx                 ; new_lo = (seed_lo * mult) + seed_hi
adc     edx, edi                 ; new_hi = overflow carry
mov     [esi+0x1C8], edx         ; store new seed high
mov     [esi+0x1C4], eax         ; store new seed low
and     eax, 3                    ; direction = seed_lo & 3  (0, 1, 2, or 3)
lea     edx, [esp+4]
push    edx
mov     edx, eax
shl     edx, 4                    ; direction * 16 (struct size)
add     edx, 0x6FDE6888          ; index into direction preset table
push    esi
mov     [esp+0xC], eax           ; save direction for later
call    0x6FD606B0               ; PlaceEndpoints(maze, &table[direction])

Equivalent C pseudocode

void AssignSummonerDirection(MazeData* maze) {

    // Step the 64-bit LCG
    uint64_t seed = (uint64_t)maze->seed_lo * 0x6AC690C5 + maze->seed_hi;
    maze->seed_lo = (uint32_t)(seed & 0xFFFFFFFF);
    maze->seed_hi = (uint32_t)(seed >> 32);

    // Pick direction: bottom 2 bits
    int direction = maze->seed_lo & 3;   // 0=N, 1=E, 2=S, 3=W

    // Look up presets for this direction and assign endpoints
    DirectionPreset* preset = &g_directionTable[direction];
    PlaceEndpoints(maze, preset);

    // For level 0x23 (35), place a second set of endpoints
    if (maze->levelId == 0x23) {
        DirectionPreset* preset2 = &g_directionTable2[direction];
        PlaceEndpoints(maze, preset2);
    }
}
Key point: The entire decision is seed & 3. Two bits of pseudorandom state. No external input, no conditional logic, no level-specific override. The same PRNG that grew the maze corridors is stepped once more, and the bottom two bits pick the Summoner arm.

2. Direction Preset Table

The direction value (0-3) indexes into a hardcoded table at 0x6FDE6888 in D2Common.dll's .rdata section. Each entry is 16 bytes (4 DWORDs):

// At VA 0x6FDE6888 in D2Common.dll (.rdata)
struct DirectionPreset {
    int dead_end_def;      // LvlPrest Def for the dead-end piece
    int summoner_def;      // LvlPrest Def for the Summoner platform
    int sentinel;           // -1 (terminator)
    int opposite_dir;       // opposite direction index
};
seed & 3 Direction Dead-End Def (v1.13c / D2R) Summoner Def (v1.13c) Opposite
0 North 265 / 517 (sanctN.ds1) 294 (summN.ds1) 3 (West)
1 East 259 / 511 (sanctE.ds1) 292 (summE.ds1) 0 (North)
2 South 261 / 513 (sanctS.ds1) 293 (summS.ds1) 1 (East)
3 West 258 / 510 (sanctW.ds1) 291 (summW.ds1) 2 (South)

The PlaceEndpoints function (at 0x6FD606B0) walks the completed maze, finds each dead-end room, checks which direction it faces, and assigns it either the Summoner platform preset or the dead-end preset from the table. The arm matching the selected direction gets the Summoner; the other three arms get dead-ends.

3. The PRNG (Linear Congruential Generator)

Diablo 2 uses a 64-bit Linear Congruential Generator throughout its map generation system. The same PRNG drives corridor placement, room branching, and endpoint assignment.

// D2's map PRNG -- used everywhere in the DRLG
// Multiplier: 0x6AC690C5 (decimal 1791398085)
// State: 64-bit split across two 32-bit fields

uint32_t seed_lo = maze->seed_lo;
uint32_t seed_hi = maze->seed_hi;

// One PRNG step
uint64_t product = (uint64_t)seed_lo * 0x6AC690C5;
seed_lo = (uint32_t)(product + seed_hi);
seed_hi = (uint32_t)((product + seed_hi) >> 32);

// Extract result (varies by use)
int direction = seed_lo & 3;       // 2 bits for direction
int room_index = seed_lo % count;  // modulo for room selection
Seed origin: The PRNG is seeded from the game's map seed, which is assigned when the game is created. The maze corridors consume many PRNG steps as they grow. After all 61 rooms are placed, one more step is taken to pick the Summoner direction. The number of prior steps depends on the maze layout, which depends on the seed. This creates a non-trivial chain that cannot be reversed from any observable game state.

4. Why No Visual Indicator Exists

The data files confirm there is nothing to see:

Corridor Tiles Are Shared

All 4 arms draw from the same pool of corridor pieces (sanctNS, sanctEW, sanctNE, etc.). The maze generator picks variants randomly. There is no "Summoner arm corridor" piece.

No Tile Substitution

LvlSub.txt has zero entries for the Arcane Sanctuary. No tile swaps or visual variations are applied based on direction.

Center Piece Is Fixed

The center junction (sanctNSEW4.ds1) contains the portal and waypoint at fixed positions. It looks identical regardless of which arm has the Summoner.

Automap Is Uniform

AutoMap.txt defines the same tile mappings for all Arcane floor and wall types. No directional automap indicator exists.

Endpoint differences (only visible AT the endpoint)

Property Dead-End (sanctN/S/E/W) Summoner (summN/S/E/W)
File size 3,012 bytes 4,304 bytes
Wall layers 1 2
Objects GoldProxy, PlaceUniqueChest, FloorTrap ArcaneTome, 6x PlaceArcaneThingamajig
Monsters 2x wraith4 (generic) 1x Summoner spawn point

These differences are only apparent once you reach the end of an arm. From the center, all four arms are visually and structurally identical.

5. What the DS1 Tile Data Shows

The Arcane Sanctuary consists of 61 rooms of 12x12 tiles, built from DS1 preset pieces stored in data/global/tiles/ACT2/Arcane/. A total of 53 DS1 files define the maze:

Piece Type Count Connectivity Variants
Dead-end (sanctN/S/E/W) 4 Single exit 1 each (fixed)
Summoner (summN/S/E/W) 4 Single exit 1 each (fixed)
Corridor (NS, EW) 2 Straight-through 3-4 each
Corner (NE, NW, SE, SW) 4 90-degree turn 3-4 each
T-junction (NSE, NSW, NEW, SEW) 4 3-way 3-4 each
4-way (NSEW) 1 4-way 5 variants (one has portal + waypoint)

All tiles use a single tileset: Act2/Arcane/Sanctuary.dt1. The LvlPrest.txt Scan flag is only set on the NSEW junction piece (Def 524), meaning it gets automap coverage, but no directional data is encoded.

DS1 Object Comparison (parsed from binary)

# Dead-end pieces: 11 objects each
# All four are structurally identical (rotated)
sanctN.ds1: 5x GoldProxy, 3x PlaceUniqueChest, 1x FloorTrap, 2x wraith4
sanctS.ds1: 5x GoldProxy, 3x PlaceUniqueChest, 1x FloorTrap, 2x wraith4
sanctE.ds1: 5x GoldProxy, 2x PlaceUniqueChest, 1x LargeChestR, 1x FloorTrap, 2x wraith4
sanctW.ds1: 5x GoldProxy, 3x PlaceUniqueChest, 1x FloorTrap, 2x wraith4

# Summoner pieces: 8 objects each
summN.ds1: 6x PlaceArcaneThingamajig, 1x ArcaneTome, 1x monster_spawn
summS.ds1: 6x PlaceArcaneThingamajig, 1x ArcaneTome, 1x monster_spawn
summE.ds1: 6x PlaceArcaneThingamajig, 1x ArcaneTome, 1x monster_spawn
summW.ds1: 6x PlaceArcaneThingamajig, 1x ArcaneTome, 1x monster_spawn

6. Community Research Confirmation

Multiple independent efforts have reached the same conclusion through empirical testing:

squeek502/d2-map-investigation by squeek502
Programmatically iterated thousands of map seeds, dumped all preset/tile/room data, and searched for correlations between the Summoner direction and any observable feature in Acts 1-2. Built on a modified version of the slashdiablo maphack to extract data via DLL injection into running game instances. Result: "no matching presets outside of things like The Summoner himself."
PureDiablo: "Lets Unlock Summoner Direction" by nkw1 and the PureDiablo community
Multi-year community investigation testing theories about waypoint orientation, tile patterns, portal position, arm order, and Canyon of the Magi map features. Dozens of contributors collected data across hundreds of runs, testing every proposed visual indicator. No reliable pattern was ever found.
kirbystealer/arcane-d2 by kirbystealer
Extracted the D2 maze generation code into a standalone TypeScript/C toolkit that runs the DRLG outside the game and dumps output as JSON. Includes binary parsers, a map generation API, memory reader, and packet sniffer. Confirms the maze and endpoint assignment are fully deterministic from the seed, with no external factors.
galaxyhaxz/d2src by galaxyhaxz
Reverse-engineered Diablo II source code precursor for v1.00, producing a binary-exact Game.exe when assembled with Visual Studio 6. While the project was abandoned due to the scale of D2's codebase, it established foundational knowledge of D2's internal structures that informed later research efforts.
diasurgical/devilution by galaxyhaxz (and contributors)
Complete reverse engineering of the original Diablo (1996). While not D2-specific, this project documented Blizzard North's DRLG patterns and coding conventions, providing a reference frame for understanding D2's maze generation architecture.

All of these findings are consistent with what the disassembled code shows: rand() & 3, with no side-channel or observable indicator. Credit is due to these researchers for years of work arriving at the same conclusion from the empirical side that the disassembly now confirms from the code side.

7. Binary Protection (D2R vs Classic)

This analysis was only possible because classic D2's DLLs are unprotected. D2R uses a completely different protection scheme:

Property Classic D2 (v1.13c) D2R (v3.1)
Binary format Separate DLLs (D2Common.dll, D2Game.dll, etc.) Single executable (D2R.exe)
Code section entropy 6.45 (normal compiled code) 8.00 (maximum -- fully encrypted)
Protection None Eidolon (Blizzard anti-tamper)
Disassembly Possible Impossible (static)
Loader Standard Windows PE D2R_loader.dll (exports: eidolon_run)
Data section (.rdata) Readable Readable (strings intact, code encrypted)

The D2R executable's .rdata section is not encrypted, which allowed us to find string references like "killed summoner for quest", "DrlgType", and "LvlMaze". But the actual code that uses these strings is in the encrypted .text section and cannot be statically analyzed.

The classic D2 v1.13c DRLG algorithm is believed to be functionally identical to D2R's, as the maze generation system was not rewritten for the remaster. The same LvlPrest/LvlMaze/LvlTypes data files drive both versions (with updated Def numbering).

8. Data File References

Analysis performed on D2R v3.1.91735 (CASC data extraction) and classic D2 v1.13c (DLL disassembly). All data extracted from local game installations using PyCASC and capstone. No online sources consulted for game data. Community research links provided for independent corroboration only.