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)