Add zones and building doc

This commit is contained in:
Jared Miller 2026-02-10 22:48:40 -05:00
parent 01d5848178
commit da809bea31
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C

View file

@ -0,0 +1,596 @@
zones and building — from grids to worlds
=========================================
a design doc. captures the vision for how players and builders create,
connect, and inhabit spaces in the MUD. born from a riffing session, fed
by research into how other engines handle this. living document — update
as decisions get made and things get built.
see also: ``things-and-building.rst`` (item/verb system),
``architecture-plan.txt`` (engine/content boundary),
``prompt-system.txt`` (modal prompts).
the core idea — everything is a zone
--------------------------------------
a zone is a rectangular grid of tiles. it has dimensions, a palette, and
tile data. that's it. everything is a zone.
- the procedural overworld? a zone with a generator (perlin noise)
- a hand-built treehouse? a zone with tile data from a file
- a single room (office, closet)? a small zone
- a dungeon? a zone with procedural guts
- the flower you spawn in? a tiny zone
the differences between zones are properties, not types:
**wrapping**
does the zone loop? options: toroidal (wraps both axes, like a sphere
— walk far enough and you're back), cylindrical (wraps one axis),
bounded (hard edges, you hit a wall or void).
**generator**
how tiles get filled. ``none`` means blank canvas (you paint it).
``perlin`` means procedural terrain from a seed. ``file`` means tile
data loaded from a TOML. generators and hand-placed tiles can coexist
— generate a base, then paint over specific areas.
**dimensions**
5x5, 100x50, 1000x1000. whatever the space needs.
**palette**
what each symbol means. char + name + color + properties (passable,
movement cost, opacity, etc). every zone has a palette. zones can
share palettes or define their own.
zone data format
-----------------
the web editor already saves something close to this. extend it with
zone metadata and portal definitions.
::
[zone]
name = "treehouse"
description = "a treehouse nestled in the canopy of an ancient oak"
wrapping = "bounded"
spawn = [10, 7]
[map]
width = 20
height = 15
data = """
..oooo..TTT.........
..o..o..TTT.........
..o..o..TTT.........
..oooo....T.........
..........T.........
........+.T.........
..........T.........
..........TTTTT.....
................~~~~
................~~~~
....................
....................
....................
....................
....................
"""
[palette]
entries = """
o|wall|#8b4513|solid
.|floor|#d2b48c|passable
T|leaves|#0a5f0a|passable
~|water|#2244aa|solid
+|door|#c8a456|passable
"""
[[portals]]
x = 10
y = 14
target = "zone:forest_path:20,5"
label = "a rope bridge stretches into the canopy"
[[portals]]
x = 10
y = 0
target = "overworld:450,300"
label = "a ladder descends to the ground far below"
zones live in ``content/zones/``. loaded at startup like mobs and
commands. hot-reloadable via ``reload``.
player location model
----------------------
today a player is ``(x, y)`` on the overworld. with zones, a player is
either on the overworld OR in a zone.
conceptually: ``(zone_name, x, y)`` where ``zone_name`` is ``None`` for
the overworld. the viewport, movement, look — all the same mechanics,
just reading from a different grid.
portals are tiles with a target. step on one, you transition. your
location changes from ``(treehouse, 10, 14)`` to
``(forest_path, 20, 5)``. the viewport redraws with the new zone's
tiles and palette.
open question: do portals auto-trigger (step on tile = transition) or
require explicit action (``enter``, ``use door``)? could depend on the
portal — a path auto-triggers, a locked door requires a key and
``open door`` + ``enter``. the portal definition could specify this::
[[portals]]
x = 5
y = 3
target = "zone:basement:5,0"
trigger = "auto" # step on it and you go
label = "stairs descend into darkness"
[[portals]]
x = 12
y = 7
target = "zone:vault:0,0"
trigger = "manual" # requires 'enter' command
label = "a heavy iron door"
requires = "iron_key" # item check (future)
the flower spawn
-----------------
new player experience. you wake up inside a sealed flower.
the flower is a tiny zone, maybe 7x7. petals surround you. the
description mentions perfume, texture on your hands, dim light filtering
through. you can look around but every direction is sealed — petal walls.
any force command (push, kick, punch, pull) opens the flower. the petal
tiles change (solid → passable, or the zone swaps entirely), you "fall"
— a text transition, maybe a brief animation of symbols scrolling — and
land in the treehouse zone.
the treehouse is the real starting area. hand-built, 20x15 or whatever
feels right. has a few things to interact with, maybe a note or a simple
puzzle. eventually you find the way down (a portal) and land on the
overworld. infinite procedural landscape in every direction.
tutorial funnel: tiny space → medium space → infinite space.
::
flower (7x7, sealed)
↓ player breaks out
treehouse (20x15, hand-built)
↓ portal (ladder, rope, etc)
overworld (1000x1000, procedural)
↓ portals to other zones
dungeons, houses, other players' creations...
creating zones — three workflows
----------------------------------
all three produce the same format. interchangeable.
workflow A: web editor
~~~~~~~~~~~~~~~~~~~~~~~
open the map editor in a browser. paint tiles, define palette, set
dimensions. save as TOML. drop the file in ``content/zones/``. reload
in the MUD (or restart). the zone exists.
the web editor is not special — it's just faster for large-scale work.
photoshop vs MS paint. good for: laying out a big area, precise pixel
work, working offline, iterating visually.
workflow B: in-game commands
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
@new-zone jareds-house 100 50
creates a blank bounded zone. you're placed in the center. the viewport
shows void/empty tiles. now you build.
::
@new-zone the-wilds 500 500 --wrap
creates a toroidal zone. walk far enough and you loop.
::
@new-zone dungeon-3 40 40 --gen perlin
creates a zone with procedural terrain. paint over specific parts.
workflow C: paint mode
~~~~~~~~~~~~~~~~~~~~~~~
this is the one that buzzes. push into paint mode like editor mode or IF
mode. your ``@`` becomes a cursor. movement works freely — no passability
checks, you float over everything.
two states within paint mode:
**survey** (default)
move around, look at things, nothing changes. scouting.
**painting**
every tile you leave behind gets stamped with your brush. walk east
5 tiles, leave 5 walls behind you.
toggle with ``p``. status line shows state::
[paint: . floor] survey
[paint: o wall] PAINTING
change brush: type the symbol character. type ``o`` and your brush is
now wall. type ``.`` for floor. type ``~`` for water. single-char input
sets the brush — no ambiguity because you're in a special mode where
normal commands don't apply.
or explicit: ``b o`` for brush=wall. both work.
you could even render as the brush symbol instead of ``@`` when painting.
you see an ``o`` moving around leaving ``o``'s behind it.
entering and exiting::
@paint enter paint mode (survey by default)
p toggle painting on/off
o set brush to 'o' (wall)
n/s/e/w/ne/etc move (and paint if painting)
@paint exit paint mode (or :q, or @done)
workflow D: @dig (expanding the canvas)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
not room-linking like traditional MUDs. you're extending the zone's tile
grid itself.
::
@dig 5 e zone grows 5 tiles east (new tiles are void)
@dig 10 s zone grows 10 tiles south
@dig n grow 1 tile north (default)
new space is void/empty. paint it into existence.
this means zones aren't fixed-size — they grow as you build. start with
a 1x1 zone and @dig your way to a mansion.
materials and palettes
-----------------------
every symbol needs at minimum: char, name, color. that's the palette.
::
[palette]
entries = """
.|floor|#d2b48c|passable
o|wall|#8b4513|solid
~|water|#2244aa|solid
+|door|#c8a456|passable
#|window|#87ceeb|solid
^|mountain|#666666|solid
T|tree|#0a5f0a|passable
:|gravel|#9a9996|passable
=|brick|#a51d2d|solid
"""
each zone has a palette. could be inline or reference a shared one::
[zone]
palette = "default" # use content/palettes/default.toml
a shared palette means consistent symbols across zones. a custom palette
means a zone can redefine what ``~`` looks like (lava instead of water).
future properties per material: movement cost (forest is slower), opacity
(walls block line of sight), damage (lava hurts), wetness (water makes
you wet). for now, just passable/solid is enough.
portals connect zones
----------------------
a portal is a tile with a target. defined in the zone TOML or placed
in-game.
in-game portal creation::
@portal here → jareds-house:5,3
"this tile, right where I'm standing, is a portal to jareds-house at
coordinates 5,3."
portals are bidirectional by default? or one-way? probably one-way by
default (you set up both ends), with a convenience flag for
bidirectional::
@portal here ↔ jareds-house:5,3 # creates both sides
what stepping on a portal looks like: the viewport dissolves (or snaps)
to the new zone. you get a brief transition message. your prompt updates
if the zone has different ambient info.
connecting to the overworld
----------------------------
the overworld is just another zone — the big one. portals from the
overworld into smaller zones work the same way. a tile at (450, 300) on
the overworld has a portal to ``treehouse:10,0``. the treehouse has a
portal back to ``overworld:450,300``.
buildings, dungeons, caves, interiors — all zones with portals from
the overworld. the overworld tile might show a special symbol (a door
icon, a cave mouth) to hint that something's there.
procedural as a tool, not the default
---------------------------------------
the procedural perlin world is useful but it's a tool. good for:
- wilderness areas (forests, plains, mountains)
- dungeon generation (random layouts with a seed)
- filler terrain between hand-built areas
- prototyping (generate a base, paint over it)
the main game world should be hand-built. the starting experience, key
locations, cities, landmarks — all authored by humans. procedural fills
in the gaps, provides variety, makes the world feel larger than what was
hand-crafted.
a zone can mix both: generate perlin terrain, then overlay hand-placed
features (roads, buildings, rivers) on top. the generator runs first,
then the overlay applies.
layers (future)
----------------
jared mentioned up/down and layers. a zone could have a z-axis — floor 1
is the ground, floor 2 is the second story, floor -1 is the basement.
same x,y grid, different z level. portals (stairs, ladders, holes)
connect layers vertically.
not needed for v1 but the data model should not prevent it. a zone's
tile data could eventually be::
[map]
width = 20
height = 15
layers = 3
data.0 = """...""" # ground floor
data.1 = """...""" # second floor
data.-1 = """...""" # basement
or layers could just be separate zones with portals. simpler, same
result. decide later.
what we learned from other engines
-----------------------------------
research into how DikuMUD, LPC, MOO, Evennia, and others handle
building. key takeaways and things to avoid.
what works
~~~~~~~~~~~
**MOO's immediacy.** in LambdaMOO, @dig creates a room and you're done.
no save step, no compilation, no restart. changes are instant. builders
stay in flow. we want this — @new-zone, @paint, and your changes are
live.
**evennia's XYZGrid.** optional coordinate system drawn as ASCII maps in
python files. rooms at grid intersections, links along paths. transition
nodes connect separate maps. proves grid-based worlds work in a
room-based engine. their wilderness contrib is smart: players get their
own room instance at a coordinate, share when co-located.
**diku OLC menus.** redit/oedit/medit push you into an edit mode with
numbered options. simple, discoverable, no memorization. the nested
mode pattern (game → editor → description editor) maps well to our mode
stack.
**LPC file-based content.** builders write .c files, ``update`` compiles
them live. version controllable, diffable, shareable. our TOML approach
has the same benefits without requiring a programming language.
**ranvier's hybrid coordinates.** optional (x,y,z) per room, local to
each area. explicit exits override inferred ones. areas can mix
coordinate and non-coordinate rooms. flexible and clean.
**dead souls QCS.** wizard-driven creation (``addnpc``, ``addroom``)
generates LPC files via interactive prompts. lowers the barrier —
builders answer questions instead of writing code.
what people hate
~~~~~~~~~~~~~~~~~
**menu-driven OLC is clunky.** everyone acknowledges OLC is better than
manual file editing, but it's still tedious. numbered menus, nested
sub-menus, explicit save commands. modern builders expect something
faster. our paint mode is a direct response to this.
**descriptions nobody reads.** "most players don't read the descriptions
after seeing them the first time, and probably only skim them the first
time anyway." high effort, low return. our tile-based visual approach
sidesteps this — you SEE the room, you don't read a paragraph about it.
descriptions can supplement the visual, not replace it.
**too much coding required.** evennia requires python for anything beyond
basic building. MUSHcode has a brutal learning curve. MOO programming is
its own language. builders want to create content, not learn to program.
our approach: TOML data files for definitions, paint mode for visuals,
minimal programming needed for basic building.
**collaborative building is an afterthought.** evennia's default setup
doesn't support collaborative building well. MOO's permission system
creates unexpected barriers. we should design for collaboration from the
start — multiple builders in a zone, clear ownership, easy sharing.
**framework lock-in.** evennia's ObjectDB forces assumptions that don't
fit all games. our zone model is simple data (TOML files, tile grids) —
no complex object hierarchy to fight against.
**no good copy-paste or templating.** every room built from scratch.
repetitive work. our web editor handles this naturally (select, copy,
paste). in-game: could support @copy-region or stamp/template tools.
**saving and losing work.** diku OLC requires explicit saves; crash
before saving = lost work. MOO's auto-persistence is better. our zones
should auto-save on edit (or at least on leaving paint mode).
**recruiting builders is hard.** the harder the tools, the fewer
builders you get. "building can be an enjoyable activity, but it's got
its downsides. which probably explains why MUD creators have a difficult
time recruiting dedicated builders." paint mode should make building
feel like play, not work.
patterns from zone connectivity
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**evennia XYZGrid transition nodes.** metadata-only nodes that hold
target coordinates on another map. never spawn as rooms. clean
abstraction for zone-to-zone links. similar to our portal concept.
**diku inter-zone exits.** reference format: ``room_name@zone_name``.
globally unique vnums (virtual numbers). our equivalent:
``zone_name:x,y``.
**evennia wilderness fixed locations.** certain wilderness coordinates
are "fixed rooms" — reach those coords, transition into a real room.
this is how building entrances work in their wilderness system. maps
directly to our portal-on-overworld-tile concept.
**alex kallend's wilderness (1999).** generate descriptions
deterministically from coordinates. same coords always produce same
room. memory-efficient. this is basically what our perlin generator
does.
**ranvier cross-area linking.** reference format: ``areaname:roomid``.
works between coordinate and non-coordinate rooms. our format:
``zone:name:x,y`` or ``overworld:x,y``.
.. note::
open research: dig deeper into other MUD codebases for building
patterns. specifically:
- evennia's prototype/spawn system for templating objects across zones
- how discworld (LPC) handles its massive hand-built world
- AresMUSH's web-based building tools (they moved building to the browser)
- graphicMUD's tile-based building (closest to our model — they use
sixel graphics on a tile grid)
- how any engine handles "undo" for building mistakes
- permission models that actually work for collaborative building
without being bureaucratic
build order
------------
rough priority. each step is usable on its own.
1. **zone data model + registry.** load TOML zone files from
``content/zones/``. zone has dimensions, tile grid, palette, portals,
metadata. registry maps zone names to loaded zones.
2. **player-in-zone tracking.** extend player location from ``(x, y)``
to ``(zone, x, y)``. zone=None means overworld. persist to sqlite.
3. **viewport reads from zone.** ``look`` checks which zone the player
is in and reads tiles from that zone's grid instead of the overworld.
movement checks that zone's passability rules. same rendering
pipeline, different data source.
4. **@new-zone + @goto.** create blank zones from in-game. ``@goto
treehouse`` teleports you into a zone. ``@goto overworld`` returns
you. builder/admin commands.
5. **paint mode.** push onto mode stack. cursor movement without
passability. brush selection. paint toggle. status line integration.
6. **file import/export.** load zone from TOML file (web editor output).
save zone state back to TOML. the web editor and in-game building
produce the same files.
7. **portals.** portal definitions in zone TOML. portal tiles trigger
zone transitions. ``@portal`` command for in-game portal creation.
8. **@dig.** expand zone dimensions from in-game. grow the canvas in any
direction.
9. **procedural generator as zone option.** ``--gen perlin`` on zone
creation. generate base terrain, allow painting over it.
10. **the flower spawn.** hand-build the flower and treehouse zones. wire
the new-player experience to start in the flower instead of the
overworld center.
open questions
---------------
- should the overworld be a zone, or is it a special case? making it a
zone simplifies the code (everything goes through the same path) but
the overworld has unique properties (huge, procedural, wrapping).
probably: overworld IS a zone, just a large wrapping one with a
generator.
- zone ownership and permissions. who can edit a zone? the creator?
anyone with builder role? co-ownership? defer this until the permission
model from things-and-building.rst is designed.
- how big can a zone be before performance matters? a 1000x1000 zone is
1MB of chars. loading, saving, and transmitting that is fine. but do
we want to support zones larger than that? probably not initially.
- persistence model. are zones purely file-based (edit file, reload) or
do runtime changes (paint mode edits) persist automatically? leaning
toward: paint mode edits modify in-memory state AND write back to the
TOML file. the file is always the source of truth, and it stays
current.
- what symbol is void/empty? tiles that haven't been painted yet. could
be space (invisible), could be a dim dot, could be configurable. needs
to be visually distinct from any palette symbol.
- should portals be visible on the map? a special symbol, a color, a
blinking character? or do they look like their underlying tile (a door
looks like a door, not like a portal)?
- layers vs separate zones for vertical space. layers are elegant but
add complexity. separate zones with portals (stairs) are simpler and
get the same result. start with separate zones.
relation to things-and-building.rst
-------------------------------------
that doc covers the item/verb system (phase 1-2) and room system
(phase 3). zones and building here are the spatial foundation that
the item system lives on top of.
zones provide the WHERE. things provide the WHAT. a zone has tiles
(terrain, walls, floor). things sit on tiles (items, furniture, NPCs).
portals are a zone-level concept. doors with locks are a thing-level
concept (the door is a @thing with an @verb for ``open`` that checks for
a key).
build zones first (this doc), then populate them with things
(things-and-building.rst).