Skip to content

Repository files navigation

diku3d

Walk through Midgaard. The world is read at load time from the stock Merc 2.1 .are files — the same text DikuMUD has been booting off since 1991 — and built into a town you can walk around, and fight in, in a browser. Rooms, mobiles, objects, doors and their descriptions all come out of the area files; the combat comes out of Merc's own C; every surface is noise baked into a texture at boot; and the buildings are glTF models generated by the Blender scripts in tools/blender/. Nothing is downloaded and nothing is modelled by hand.

python3 serve.py            # http://localhost:8173/

There is no build step. Three.js is vendored under vendor/; the app is plain ES modules.

What you are looking at

A Diku world is a graph, not a plan. North-then-east does not have to land where east-then-north does, and in Midgaard it never does: of the 24 places you can test that on the Market Square, all 24 disagree. So the layout does not try to be faithful to geometry that isn't there. It does this instead:

  1. Place — breadth-first from the Temple of Midgaard (#3001), each room taking the cell its exit asks for, or the nearest free one that satisfies the most of its other exits.
  2. Relax — sweep back over the result offering every room the free cells nearby, keeping any move that joins up more exits than it breaks.
  3. Spread — rooms keep the even coordinates, so every cell between two rooms is free to build a street through.
  4. Route — each exit is walked through those free cells, at most six of them, setting off in the direction the exit claims to go.

What survives is a passage you walk. What doesn't is a stone archway with a shimmer in it that puts you where the exit says — honest about the fact that the mud's geography folds. For Midgaard that leaves 93% of exits walkable and eight archways; across all 43 stock areas it averages 94%.

A cell is thirteen metres, which is far too wide for a street, so an open-air city room does not pave all of it: every side with no way out of it brings the frontage behind it forward, and the corners between two ways out are built on too. Four exits leaves a crossroads with a block on each corner, two opposite exits leaves a lane between two terraces, and either way the facades finish 6.6 m apart. Squares are exempt, by the mud's own name for them.

node tools/layout-check.mjs                    # every area
node tools/layout-check.mjs 3001 midgaard.are  # one, from a given room
node tools/parse-check.mjs                     # the reader, over everything

Anything still unreachable — an exit into an area you haven't loaded — becomes a barred gate with a sign saying which vnum is on the other side.

Controls

W A S D walk, shift to run, space to jump
arrow keys take the exit north, east, south or west, and turn to face that way — the mud's own navigation, so north is north whichever way you were looking. Your pace carries over: step east while walking and you keep the same speed, now heading east
page up / page down take the exit up or down, leaving you looking where you were
mouse look, E to examine what you're looking at or open a door; E again closes it
14 dawn, noon, dusk, night
P cycle quality: low, medium, high, max
F frame rate, draw calls, triangles, GPU milliseconds
V noclip
M sound
G force every lock in the world
left mouse attack what you are facing
I B K R T Q inventory, trade, skills, rest, take, recall
esc release the mouse

URL parameters

?areas=midgaard,school which .are files to load (default midgaard)
?room=3001 where to start, and what the layout is built around
?max=400 stop placing after this many rooms
?time=dusk dawn, noon, dusk, night
?quality=medium low, medium, high, max
?fps=30 override the preset's frame cap; 0 uncaps it

Try ?areas=moria&room=7000, ?areas=sewer&room=7100, ?areas=haon&room=6500 for somewhere that is not a town. Loading two areas that connect (midgaard,school) opens the gate between them.

Midgaard alone is a town with its edges cut off. Several of its exits lead into other area files, and with only midgaard loaded those say #3504 is in an area not loaded. They are real ways out of the city, not dead ends:

?areas=midgaard,midennir,sewer

opens the Dump's south exit onto the South Bridge and its down exit into the sewer, at 323 rooms — still inside the default ?max=400.

A room drawn next to another on the minimap is not an exit. The map is the layout grid, and the layout is a non-Euclidean graph forced flat, so cells end up adjacent that the mud never joined. Park Road sits directly south of the Dump on the map and there is no way to walk between them.

Under the map is a mariner's card compass. The rose is fixed to the world and turns beneath the index at the top, so whatever point sits under the index is the way you are facing, and it reads upright there whichever one it is. North is the fleur-de-lis. The card is sprung, so it settles rather than snaps.

Keeping the laptop cool

The scene is around 1300 draw calls on the Market Square — still not much, and most of the cost is pixels rather than submissions. src/quality.js owns all four dials: render resolution, multisampling, the bloom chain, and the frame cap. It also measures the result on the GPU with EXT_disjoint_timer_query_webgl2 rather than inferring it from wall time, and drops the render scale a step if a frame threatens its budget.

Measured on an M4 Max at 1400×900 CSS, standing on the Market Square, with the render loop halted so nothing else is drawing:

preset renders at ms/frame fps GPU busy
low 1400×900, no bloom, no shadows, no AO 1.0 60 6%
medium 1750×1125, half-res bloom, 2048 shadows 2.5 60 15%
high (default) 2450×1575, SMAA, GTAO 12.1 30 36%
max 2800×1800, full-res bloom, GTAO, shafts 18 uncapped pegged

high was 5.8 ms before the streets were given two sides. Bringing the frontages forward roughly doubled the geometry — 2.0M triangles against 631k — which is a third of the 30 fps budget and worth every millisecond.

Release the mouse or switch tabs and it drops to 10 fps; hide the tab and it stops drawing altogether. The sun's shadow map is snapped to a six-metre grid and only redrawn when you cross a line or the time of day changes, instead of every frame.

F shows the live GPU figure. On a lightly loaded frame it reads higher than the table above, because the GPU drops to a low clock between frames and the same work takes longer — which is the point.

WebGPU would not help here. Its win is CPU-side driver overhead across thousands of draw calls; this scene submits ~300 in well under a millisecond. The port is also not free: the flame, water and sky shaders and the whole post-processing chain are WebGL-only and would have to be rewritten as TSL node materials.

Layout of the source

src/are.js reader for the Diku/Merc area format, and the reset table
src/layout.js room graph → grid: place, relax, spread, route
src/textures.js every material, baked from noise into albedo/normal/roughness
src/build.js cells → geometry, merged per chunk per material
src/actors.js mobiles, objects, fire, foliage, doors, signage
src/player.js first-person movement over platforms and boxes
src/quality.js frame budget: resolution, bloom, shadows, frame cap, light pool
src/render.js sky-based image-based lighting, ambient occlusion, light shafts
src/assets.js loads the Blender models and instances them per chunk
src/game.js Merc 2.1's combat, experience, loot, shops and gate wardens
src/game-ui.js the game's own overlay: vitals, target, combat log
tools/blender/ the scripts that generate assets/*.glb
src/hud.js room text, minimap, compass
src/audio.js wind, footsteps, doors, a bell — synthesised, no files
merc21/ Merc 2.1 as released on 1 August 1993, unmodified

window.diku is exposed in the console: diku.goto(3014), diku.look(x,y,z,yaw), diku.applyTime('night'), plus layout, built, world, game and quality. Set diku.state.benchmark = true to halt the render loop before measuring.

Running the mud itself

The Merc server in merc21/ still compiles. On macOS it needs two flags and no source changes:

cd merc21/src
make CC=cc NOCRYPT="-DNOCRYPT -Dunix" L_FLAGS="-O"
cd ../area && ../src/merc 4400        # telnet localhost 4400

-Dunix because Apple clang defines __unix__ but not bare unix, which is what merc.h tests when it picks /dev/null to hold a spare stream handle. -DNOCRYPT plus dropping -lcrypt because macOS has no libcrypt — passwords are then stored in the clear, which is fine on loopback and nowhere else.

Known limits

  • Mobiles fight, die, drop what the reset table gave them and trade at their shop's own margins, but they do not walk their routes or run their spec_funs.
  • The judge that reviews the screenshots does not pass this build. What is left on its list: interiors are all 10.26 m square with a 5.2 m ceiling and are empty of the furniture their own descriptions promise, the sun is still the only shadow-casting light, water reflects nothing and the river is built from stepped slabs with visible seams, the half-timbering is one module repeated, and the trees are three ellipsoids on a stick. The last two need the Blender scripts rerun, not a code change.
  • Two staircases in one room share a single opening in the ceiling.
  • An exit whose room has no free wall left ends up as an archway standing in the middle of the floor.
  • The viewer never talks to the running mud. A websocket-to-telnet bridge would make that possible; nothing here assumes it.

Credits

DikuMUD is copyright 1990, 1991 Hans Henrik Stærfeldt, Katja Nyboe, Tom Madsen, Michael Seifert and Sebastian Hammer, of DIKU, the department of computer science at the University of Copenhagen. Merc 2.1 is by Furey, Hatchet and Kahn, released 1 August 1993. Their licences are in merc21/README and merc21/doc/, and both forbid commercial use — that applies to this too. The code in this repository is otherwise MIT (LICENSE); the vendored three.js in vendor/three/ carries its own MIT notice, and the merc21/ submodule keeps the DikuMUD and Merc terms.

About

Walk through Midgaard. The world is read at load time from the stock Merc 2.1 .are files — no build step, every texture and sound generated at boot.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages