Add object model design doc

This commit is contained in:
Jared Miller 2026-02-11 12:12:30 -05:00
parent 94a01186c8
commit 400ebe4275
Signed by: shmup
GPG key ID: 22B5C6D66A38B06C
2 changed files with 251 additions and 0 deletions

250
docs/how/object-model.rst Normal file
View file

@ -0,0 +1,250 @@
object model — containment, verbs, and the location tree
========================================================
a design doc for the object hierarchy that powers containment, inventory,
zones, and verb-driven interaction. captures the core rule, the class
structure, and how everything connects.
see also: ``things-and-building.rst`` (@thing/@verb decorator system),
``zones-and-building.rst`` (zone data format), ``architecture-plan.txt``
(engine/content boundary), ``combat.rst`` (combat on Entity subgraph).
the core rule
-------------
every object has a ``location`` attribute that points to another object, or
None. that's the entire containment system.
where something is = what its location points to. None means it's a top-level
thing — a zone, a template that hasn't spawned yet, or something removed from
the world.
here's what the tree looks like::
overworld zone (location=None)
├── player "jared" (location=overworld, x=50, y=30)
│ ├── sword (location=jared) ← inventory
│ └── backpack (location=jared) ← inventory
│ ├── gem (location=backpack) ← nested
│ └── potion (location=backpack)
├── oak tree (location=overworld, x=50, y=31) ← fixture
└── portal (location=overworld, x=52, y=30) ← leads to tavern zone
tavern zone (location=None)
├── bartender mob (location=tavern, x=3, y=2)
├── chair (location=tavern, x=1, y=1) ← fixture
└── mug (location=tavern, x=3, y=3) ← item on the floor
``contents`` is the reverse query — "give me everything whose location is me."
spatial position (x, y) is optional. objects in zones have coordinates. items
in inventory don't. a sword in your backpack has location=backpack, x=None,
y=None.
the hierarchy
-------------
::
Object (name, location, x, y) ← @verb targets this
├── Zone (width, height, toroidal, terrain)
├── Entity (combat stats)
│ ├── Player (session stuff)
│ └── Mob (ai stuff)
├── Thing (description, portable)
│ ├── Container (capacity, closed, locked)
│ └── Portal (target_zone, target_x, target_y)
└── (Exit — maybe, if portals don't cover it)
**Object**
base class for everything in the world. has name, location (Object or
None), optional x/y for spatial position within location. the @verb
decorator targets this — anything can have verbs. defines ``can_accept(obj)``
method (default: False) for controlling what can be placed inside.
**Zone**
a spatial area with a grid. the overworld, a tavern interior, a pocket
dimension. location=None (top-level). has terrain, dimensions, toroidal
flag. ``can_accept`` returns True (zones accept everything). defines
``contents_at(x, y)`` for spatial queries.
**Entity**
something that fights. adds combat stats (PL, stamina, defense timing).
``can_accept`` allows portable Things (inventory).
**Player**
a connected human. adds writer, mode_stack, caps, editor, if_session.
subclass of Entity.
**Mob**
AI-controlled entity. adds moves list, behavior. subclass of Entity.
**Thing**
an interactive object. adds description, portable flag. the @thing
decorator targets this for spawnable templates.
**Container**
a Thing that explicitly accepts contents. adds capacity, closed, locked
state. game-facing behaviors (open/close, lock/unlock, put/get).
**Portal**
a Thing that connects zones. adds target_zone, target_x, target_y.
portable=False.
**Exit**
not needed yet. portals + zone grids might cover all transitions. parked
until we discover it's necessary.
note: Fixture was considered as a class but we just use
``Thing(portable=False)`` instead. no shared behavior that warrants a class.
containment mechanics
---------------------
the containment MECHANISM is on Object (any object can technically be a
location via the location pointer). but the GAME restricts it via
``can_accept()``:
- Zone: accepts everything
- Entity: accepts portable Things (inventory)
- Container: accepts items up to capacity, if not closed
- Thing: rejects by default (can't put a sword inside another sword)
- Object base: rejects by default
this means containment is a capability, not a class. follows the Evennia/LPC
pattern. the Container class just bundles the common container game behaviors
— it's not special from a tree perspective.
location=None
-------------
three things have location=None:
1. **zones** — they're top-level containers. the overworld, tavern zone, etc.
2. **templates/prototypes** — things that exist but aren't placed anywhere yet.
a mob template, an item blueprint. they're in code or data files but not
spawned into the world.
3. **despawned objects** — removed from the world but not deleted. a mob dies
and goes back to location=None until respawn.
this is first-class. a mob template at location=None is the prototype. spawn
it = clone with location=zone. dies = back to location=None until respawn.
no special flags needed, just the natural state.
verbs on everything
-------------------
@verb targets Object, not Thing. everything in the world can have verbs:
- "pet" a mob (verb on Entity)
- "open" a chest (verb on Container)
- "drink from" a fountain (verb on Thing with portable=False)
- "dig" in a zone (verb on Zone)
- "high-five" a player (verb on Entity)
the verb system doesn't care about the class hierarchy. if it's an Object and
has a verb registered, players can use it.
how existing code maps in
--------------------------
Entity(name, x, y) → Entity(name, location=zone, x=5, y=10)
entities gain a location field. x/y stay but become relative to location.
Player/Mob → same, just gains location field from Object parent
existing code largely unchanged. location replaces implicit zone references.
overworld terrain grid → becomes a Zone object
the procedural perlin overworld becomes a Zone instance. toroidal, with
generator=perlin.
planned zones (tavern, tutorial) → each a Zone object
hand-built zones like the treehouse. bounded or toroidal, tile data from
TOML files.
planned portals → Portal things inside zones
portals are objects with target_zone, target_x, target_y. live inside zones
like any other Thing.
@thing PepsiCan from things-and-building doc → Thing subclass
same as planned. @thing decorator creates Thing templates. spawn them into
zones or player inventory.
combat encounters → still between Entities, unchanged
combat operates on the Entity subgraph. location doesn't matter for combat
resolution, only proximity (same zone, nearby x/y).
IF sessions → still on Player, unchanged
if_session lives on Player. the IF subprocess doesn't care about location.
mode stack → still on Player, unchanged
mode_stack is a Player concern. doesn't interact with the object tree.
the migration is gentle. Entity gains a location field and x/y become
optional (None when inside a non-spatial container like a backpack).
what this enables
-----------------
- **inventory** — put sword in backpack, backpack in player. nested
containment via location pointers.
- **items on the ground** — sword at location=zone, x=5, y=10. visible to
anyone at those coordinates.
- **furniture and fixtures** — Thing with portable=False. a fountain, a tree,
a statue. stays put, but can have verbs.
- **containers** — chests, bags, locked doors. Container subclass handles
capacity, closed, locked state.
- **zone transitions** — portals as objects. step on portal tile, location
changes to target zone.
- **mob templates and spawning** — location=None is the template. spawn =
clone with location=zone. dies = back to None.
- **verb-driven interaction on anything** — pet the cat, read the sign, dig
the ground. @verb on Object means no restrictions.
- **the "phone switch" navigation zone** — a zone full of portals. walk to
a portal, step through, appear in that world. the hub.
open questions (parked)
-----------------------
- **equipment slots** (wield, wear) — additive later when combat needs it. do
we track worn/wielded separately from inventory, or is it just metadata on
the Thing? defer until combat design demands it.
- **Exit class** — may not be needed. portals might cover it. if we discover
a need for one-way passages with different behavior than portals, revisit.
- **persistence** — which objects persist to SQLite, which are runtime-only?
player inventory persists. zone state (tile changes from paint mode)
persists to TOML. mob instances probably don't persist (respawn from
templates). decide per-class as we implement.
- **object registry implementation** — global dict? spatial index? contents
cache? probably: zones maintain contents_at(x, y) lookups, global registry
maps object IDs to instances for verb resolution.
related docs
------------
- ``things-and-building.rst`` — @thing/@verb decorator system, content layer
on top of this model
- ``zones-and-building.rst`` — zone data format, building tools, paint mode
- ``architecture-plan.txt`` — overall engine/content boundary
- ``combat.rst`` — combat system (operates on Entity subgraph)

View file

@ -19,6 +19,7 @@ engine
- ``docs/how/terrain-generation.txt`` — perlin noise, elevation thresholds, river tracing
- ``docs/how/things-and-building.rst`` — interactive objects, python class decorators
- ``docs/how/zones-and-building.rst`` — zones as spatial unit, procedural overworld, player homes
- ``docs/how/object-model.rst`` — object hierarchy, containment, verbs, location system
- ``docs/how/prompt-system.txt`` — modal prompts, color markup, per-player customization
combat