Files
lore-engine/docs/17-planes.md
Hermes Agent 7d81a761f9 docs(v1.2): planes as first-class graph nodes (Setting, Plane, EXISTS_IN, REFLECTS, LAYER_OF, ADJACENT_TO, ACCESSIBLE_VIA)
Replaces the v1.1 'world_id is a string' model with a graph-of-planes. Driven by Kay's Q2 ('worlds are planes') and the v1.2 design review.

- 17-planes.md (NEW): the plane taxonomy, the four relation types, Cypher patterns, migration from world_id, open questions
- 01-ontology.md: Plane and Setting as first-class nodes; the 6 plane-relation edge types
- 02-time-model.md: plane-aware time (entity_planes_at_time as the 6th time-aware primitive)
- 08-architecture.md: data flow for plane questions (the 'can I get from X to Y' pattern)
- 11-extensibility.md: how to add custom planes and plane-relations without code
- 12-storage-strategy.md: planes are pure graph (no Postgres/Redis/Qdrant/S3 changes)
- 14-examples.md: example 5 — full Setting + planes + Roland + Asmodeus + LLM tool calls
- README.md: v1.2 entry + doc 17 in the table of contents

The POC rebuild (T10) is the next step: migrate the existing 4 world_id values to Setting/Plane nodes and update the plugin queries to use EXISTS_IN/LOCATED_IN.
2026-06-17 03:17:15 +00:00

18 KiB

17 — Planes of Existence: A First-Class Graph Model

Status: v1.2 amendment (replaces v1.1's flat world_id strings with graph nodes). Drives: Q1, Q2, Q3 from the design review (planes as first-class nodes; "worlds are planes"; EXISTS_IN is a timeless type-assertion; the four plane-relation edge types).

This document is the new plane model. It supersedes the v1.1 "world_id string namespace" pattern. The change is motivated by the limitations of the flat-string model when faced with D&D-style cosmology: a string can name a plane, but it cannot model the relations between planes (Shadowfell reflects Material, Astral is a transit layer, the Nine Hells has nine sub-layers).

After this amendment:

  • Plane is a first-class graph node.
  • A Setting is a parent that owns its planes.
  • Every entity has zero or more EXISTS_IN edges to Plane nodes (timeless type-assertion).
  • Every entity has zero or more time-bounded LOCATED_IN edges to Plane nodes (where it was at time T).
  • Planes have relations to other planes: REFLECTS, LAYER_OF, ADJACENT_TO, ACCESSIBLE_VIA.
  • The old world_id string property is deprecated but kept for read-only compatibility during the migration window.

The key insight: "worlds are planes"

In v1.1 we used world_id as a flat string on every node. The values in the seed data were default, arda_greyscale, mardonar, voldramir. The flaw: a "world" was a single string with no internal structure, so it could not represent the relations between worlds.

In v1.2 the model is rewritten: a "world" is just the primary Material Plane of a setting. When a player says "Mardonar," they mean the Material Plane of the Mardonari setting — not a separate universe. The same person can be in Mardonar's Material Plane today, in Mardonar's Voldramir demiplane next week, and in the Mardonari setting's Astral transit layer in between. The plane changes; the person does not get duplicated.

This is the D&D-canonical model. It also happens to be what world_id was trying to express all along, just with the wrong abstraction.

Node labels (new)

Setting

A campaign, game, or world scope. The top of the world hierarchy. A Setting is the unit of "what world are we talking about" — Eberron, the Forgotten Realms, Mardonari, the Iron Kingdoms, a home-brew demiplane-only setting.

Property Type Notes
id string Stable slug, e.g. eberron, mardonari, default (legacy alias for the v1 default world).
name string Human-readable, e.g. "Eberron," "Mardonari."
summary string One-paragraph world description.
genres string[] e.g. ["high_fantasy","steampunk","noir"].
canonical_time string The current in-fiction time of the campaign. Format: {era}.{unit} (see 02-time-model.md).

A Setting is the root of the plane tree. Every Plane belongs to exactly one Setting via HAS_PLANE. A Setting with no planes is valid (and represents a world whose plane structure is not yet detailed).

Plane

A layer of existence within or alongside a setting. The engine treats all planes uniformly — there is no special-cased Material plane. Planes are typed by their kind property, and relations between planes are first-class edges.

Property Type Notes
id string Stable slug within the setting, e.g. eberron.material, mardonari.voldramir, default.arda_greyscale.
name string Human-readable, e.g. "The Material Plane," "Voldramir," "Mount Celestia."
kind string enum See the taxonomy below.
summary string Optional one-paragraph description.
parent_plane string (Plane.id) For layered planes (Nine Hells → Dis → Minauros, etc.).
accessible boolean Whether mortals can reach the plane without artifact/spell assistance. False for the Far Realm, true for Material.
alignment_tendency string Optional, e.g. lawful_good, chaotic_evil, neutral.
valid_from string When this plane came into existence. Format: {era}.{unit}.
valid_until string When it ceased to exist. null for still-active planes.

The plane taxonomy (the kind enum)

The engine does not hard-code any specific planes. kind is a free-form string, but the recommended taxonomy is the D&D-style cosmology:

kind Meaning Examples Notes
material The primary, baseline plane. The world players normally inhabit. Eberron Material, Mardonari Material, Forgotten Realms Material. Every setting has one. The "world" colloquially.
reflection A plane that mirrors the material plane but with different qualities. Shadowfell (dark), Feywild (vibrant), the parallel Material of the Arda greyscale seed. Has a REFLECTS edge to a material plane.
transit A plane used to travel between other planes. Astral Plane, Ethereal Plane. Has ADJACENT_TO edges to other planes.
ethereal A plane that overlaps the material — you can see into both. The Ethereal Plane. Distinct from transit because the overlap is geometric, not journey-based.
outer A plane inhabited by deities, fiends, celestials, or other extraplanar beings. Mount Celestia, the Nine Hells, the Abyss, Mechanus, the Beastlands, Gehenna. Grouped by alignment tendency.
inner A plane embodying an elemental or quasi-elemental force. Plane of Fire, Plane of Water, Plane of Earth, Plane of Air, Para-elemental planes. Often paired.
demiplane A small, finite, often personalized plane. A wizard's pocket dimension, Voldramir, the demiplane created by Mordenkainen's Magnificent Mansion, Ravenloft (a demiplane that imprisons). Has a contained_in (often Material) and is finite in size.
transcendent A plane that exists outside the standard cosmology. The Far Realm (lovecraftian outer), the realm of the gods-as-concept (in settings where "the gods" is a place, not beings). Reserved for cosmology-bending settings.

A setting does not need every kind. A low-magic home-brew world might have only material + a single demiplane. A planes-heavy D&D setting has dozens.

Edge types (plane model)

HAS_PLANE (Setting → Plane)

Every Plane belongs to exactly one Setting. This is the root of the plane tree.

(:Setting {id: 'eberron'})-[:HAS_PLANE]->(:Plane {id: 'eberron.material', kind: 'material'})

EXISTS_IN (any entity → Plane) — timeless type-assertion

This edge asserts that the entity type exists in the plane. It is not time-bounded (see Q3). It answers "can this kind of thing be in this plane?" not "was it there at time T?"

(:Person {name: 'Asmodeus'})-[:EXISTS_IN]->(:Plane {id: 'eberron.nine_hells', kind: 'outer'})
(:Person {name: 'Aldric'})-[:EXISTS_IN]->(:Plane {id: 'eberron.material', kind: 'material'})
(:Person {name: 'Roland Raventhorne'})-[:EXISTS_IN]->(:Plane {id: 'mardonari.material', kind: 'material'})

EXISTS_IN is many-to-many. A god exists in multiple planes (their home, the Astral, possibly Material). A mortal typically exists in one. A plane-transcendent entity exists in many. This is the answer to "how does EXISTS_IN work for entities that exist in multiple planes?" — see "Open questions" below for the trade-off.

The relation is optional. A setting with no EXISTS_IN edges from a node means "this node's plane presence is not specified yet" (provisional). The consistency engine has a rule for this: see 04-consistency.md.

LOCATED_IN (any entity → Plane) — time-bounded

This edge says "the entity was at this plane at time T." It is time-bounded via valid_from and valid_until, exactly like the other time-bounded relations in the engine. It answers "where was Roland in year 312 TA?" not "what kind of plane is Roland from?"

(:Person {name: 'Roland Raventhorne'})-[:LOCATED_IN {
  valid_from: '3rd_age.year_425',
  valid_until: '3rd_age.year_445'
}]->(:Plane {id: 'mardonari.material'})

(:Person {name: 'Roland Raventhorne'})-[:LOCATED_IN {
  valid_from: '3rd_age.year_428',
  valid_until: '3rd_age.year_446'
}]->(:Plane {id: 'mardonari.voldramir', kind: 'demiplane'})

The same Roland can be in Material and a demiplane at different times. The Cypher pattern:

// Where was Roland in 430 TA?
MATCH (p:Person {name: 'Roland Raventhorne'})-[r:LOCATED_IN]->(plane:Plane)
WHERE time_in_window('3rd_age.year_430', r.valid_from, r.valid_until)
RETURN plane.name, plane.kind

REFLECTS (Plane → Plane) — material mirrors

A plane that is a reflection of another. Shadowfell reflects Material, Feywild reflects Material, the Arda greyscale "world" reflects Eberron's Material. A reflection is a parallel universe that mirrors the geography of its source but with twisted or inverted qualities.

(:Plane {id: 'eberron.shadowfell', kind: 'reflection'})-[:REFLECTS]->(:Plane {id: 'eberron.material'})
(:Plane {id: 'eberron.feywild', kind: 'reflection'})-[:REFLECTS]->(:Plane {id: 'eberron.material'})

This is directed. Eberron's Shadowfell reflects Eberron's Material, not Forgotten Realms' Material. The plane-id is the disambiguator.

LAYER_OF (Plane → Plane) — sub-planes

A plane that is a sub-layer of a larger plane. The Nine Hells is a single Outer Plane, but it has nine layers (Avernus, Dis, Minauros, Phlegethos, Stygia, Malbolge, Maladomini, Cania, Nessus). A layer LAYER_OF its parent plane. This is also how a demiplane LAYER_OF its containing plane if you prefer that model.

(:Plane {id: 'eberron.dis', kind: 'outer'})-[:LAYER_OF]->(:Plane {id: 'eberron.nine_hells', kind: 'outer'})
(:Plane {id: 'mardonari.voldramir', kind: 'demiplane'})-[:LAYER_OF]->(:Plane {id: 'mardonari.material', kind: 'material'})

A plane can have many layers. A layer can be a parent of further layers (Dis has sub-layers in some cosmologies). The relation is not required — a flat taxonomy is valid.

ADJACENT_TO (Plane → Plane) — geometric neighbors

A plane that is geometrically adjacent — you can physically reach it by walking, climbing, swimming, or by being there at the right moment. Material is adjacent to Ethereal (the Ethereal Plane overlaps it). Material is adjacent to the Astral in some cosmologies. Adjacency is the answer to "can I walk there from where I am?"

(:Plane {id: 'eberron.material'})-[:ADJACENT_TO]->(:Plane {id: 'eberron.ethereal', kind: 'ethereal'})
(:Plane {id: 'eberron.material'})-[:ADJACENT_TO]->(:Plane {id: 'eberron.astral', kind: 'transit'})

ADJACENT_TO is symmetric. If Material is adjacent to Ethereal, Ethereal is adjacent to Material. The Cypher pattern uses undirected traversal.

ACCESSIBLE_VIA (Plane → Spell OR Plane → Item OR Plane → Location)

A plane that is reachable by a specific mechanism. The most common form is spell: Material is ACCESSIBLE_VIA Plane Shift, Astral Projection, or Gate. It is also valid to point at a physical portal: the Nine Hells is ACCESSIBLE_VIA the Stygian Gate in Dis.

(:Plane {id: 'eberron.nine_hells'})-[:ACCESSIBLE_VIA]->(:Spell {name: 'Plane Shift'})
(:Plane {id: 'eberron.material'})-[:ACCESSIBLE_VIA]->(:Location {name: 'The Stygian Gate'})

This is the "is there a way to get there" relation. It is also the LLM-friendly one: "I'm at X, can I get to Y?" becomes a graph traversal.

The canonical plane taxonomy (a starter set for D&D-style settings)

This is a recommended starting set, not a hard requirement. The engine does not require any of these planes to exist.

Plane kind reflects adjacent_to layer_of notes
{setting}.material material ethereal, astral Required.
{setting}.shadowfell reflection material ethereal Optional. Dark mirror.
{setting}.feywild reflection material Optional. Vibrant mirror.
{setting}.ethereal ethereal material, shadowfell Overlaps material.
{setting}.astral transit material, outer planes Transit layer.
{setting}.mount_celestia outer astral Lawful good afterlife.
{setting}.nine_hells outer astral Lawful evil afterlife.
{setting}.abyss outer astral Chaotic evil.
{setting}.mechanus outer astral Lawful neutral.
{setting}.beastlands outer astral Chaotic neutral.
{setting}.far_realm transcendent Lovecraftian outer.

The {setting} prefix is a convention, not a constraint. A setting with one plane can use the bare name (material instead of eberron.material).

Cypher patterns

"What planes does this entity exist in?"

MATCH (p:Person {name: 'Asmodeus'})-[:EXISTS_IN]->(plane:Plane)
RETURN plane.id, plane.kind, plane.name

"Where was this entity at time T?"

MATCH (p:Person {name: 'Roland Raventhorne'})-[r:LOCATED_IN]->(plane:Plane)
WHERE time_in_window('3rd_age.year_430', r.valid_from, r.valid_until)
RETURN plane.id, plane.kind, plane.name

"What planes are accessible from where I am?"

MATCH (me)-[:LOCATED_IN {valid_until: null}]->(here:Plane)
MATCH (here)-[:ADJACENT_TO|ACCESSIBLE_VIA*1..2]->(target:Plane)
WHERE target <> here
RETURN DISTINCT target.id, target.kind, target.name

"What is the demiplane of Voldramir, and what contains it?"

MATCH (d:Plane {id: 'mardonari.voldramir'})-[:LAYER_OF]->(parent:Plane)
RETURN d.name AS demiplane, parent.name AS contained_in

"What reflections of Material exist in this setting?"

MATCH (material:Plane {kind: 'material'})<-[:REFLECTS]-(reflection:Plane)
WHERE material.id STARTS WITH 'eberron.'
RETURN reflection.id, reflection.kind

Migration from v1.1

The v1.1 design used world_id: string on every node as a flat namespace. The v1.2 model replaces that with the EXISTS_IN graph traversal. The migration is:

  1. Add Setting and Plane nodes for every distinct world_id value currently in the graph. default becomes Setting("default") + Plane("default.material", kind: 'material'). mardonar becomes Plane("mardonari.material", kind: 'material') under Setting("mardonari"). voldramir becomes Plane("mardonari.voldramir", kind: 'demiplane') under Setting("mardonari"). arda_greyscale becomes Plane("default.arda_greyscale", kind: 'demiplane') under Setting("default").

  2. Create EXISTS_IN edges from every entity to its primary plane. For most nodes this is a single edge. For multi-plane entities (gods, outsiders), it is many.

  3. Mark the legacy world_id property deprecated but do not delete it. Plugin queries check for EXISTS_IN first, fall back to world_id if the new edges are not present (during the migration window).

  4. Update plugins and the seed to use the new model. The seed script writes the planes; the plugins query by plane.

  5. Remove world_id property in v2.0. After the migration is stable, the string property is dropped. Plugin queries that still reference it are bugs.

Open questions and trade-offs

EXISTS_IN is many-to-many

Default choice (Q-clarify default): a node can have EXISTS_IN edges to many planes. The trade-off:

  • Many-to-many (default). A god exists in their home plane, the Astral, and possibly Material. A mortal exists in one plane but can be extended to many. Most flexible, but means a "what planes is this entity in" query can return many rows and the LLM has to disambiguate.
  • One-to-one. Each entity has a single primary plane. If it also exists in others, that is a LOCATED_IN edge (time-bounded). More restrictive, matches D&D "type" thinking ("Aldric is a Material-Plane creature").
  • One or more, but at least one. Same as many-to-many, but missing EXISTS_IN is a consistency violation rather than a provisional state.

The default is reversible — it is a Cypher pattern, not a schema migration. If a downstream user (the LLM, the world-builder) finds many-to-many cluttered, switching to one-to-one is a refactor of the seed and a small plugin change. The design note here is: when in doubt, support the more general case and let the LLM narrow.

Layer-of vs contained-in

A demiplane is contained in a plane (Voldramir is contained in Mardonari's Material). The taxonomy uses LAYER_OF for both "sub-layer" (Dis is a layer of the Nine Hells) and "contained in" (Voldramir is a layer of Mardonari's Material). An alternative is to add a CONTAINED_IN edge, but the dual semantics of LAYER_OF ("a sub-plane") cover both. The Cypher remains the same.

Plane relations that aren't in this doc

If the world needs more plane-edge types (e.g., BORDERS, TRADE_ROUTE, WAR_WITH), they are added to the ontology the same way other edge types are. The four in Q4 (REFLECTS, LAYER_OF, ADJACENT_TO, ACCESSIBLE_VIA) are the recommended starting set.

What this is not

  • Not a renaming of world_id to plane_id. The new model is a graph of planes with relations, not a string namespace. The migration is structural, not cosmetic.
  • Not D&D-specific. The taxonomy is D&D-flavored because the engine's use case is high fantasy, but the model works for any cosmology: the Wheel of Time has its own pattern (the Pattern, the One Power, the Dark One's prison — a transcendent plane), Malazan has its warrens, Cosmere has its three realms. The taxonomy is a recommendation, not a constraint.
  • Not finished. The taxonomy will grow as the engine is used. The kind enum, the relation types, the time-boundedness of EXISTS_IN (currently timeless), the migration timeline — all of these are open to revision in v2 based on what the LLM and the world-builder actually do with the model.