Saxonberg Server API
    Preparing search index...

    Base class for incorporeal game objects with identity but no physical presence.

    Hierarchy (View Summary)

    Index

    Constructors

    Properties

    _wheelPassable: boolean = true

    Whether a wheeled hauled cart may pass through. Default true; authors set false on stairs / stiles / narrow doors that admit walking but not wheels (the residue the media gate can't express).

    blocked: boolean

    Permanently blocked regardless of door state.

    direction: string
    hidden: boolean

    Hidden exits are skipped by getObviousExits() (and therefore by look).

    inverse?: default

    Counterpart Exit on the destination side, when this exit is part of a bidirectional pair. undefined for one-way exits, vessel- synthesized 'out' exits, or pairs that haven't been wired (lazy- load case).

    media: string[] = []

    Locomotion media this exit admits (e.g. ['ground'], ['water', 'ground']). The exit admits any LocomotionMode whose getMedium() is in this list. Default [] is treated by allowsMode as legacy walk-only — preserves backcompat for exits authored before the medium refactor. Authors group modes by medium to keep declarations terse: a normal corridor's ['ground'] admits every ground-locomotion mode without enumerating each verb.

    messageIn: string | null

    Custom arrival text (destination peers). null → use default.

    messageOut: string | null

    Custom departure text (source peers). null → use default.

    muffled: boolean

    Muffled exits: movement/speech through here is suppressed for sensors. Reserved for later phases.

    noFollow: boolean

    If true, followers/mounts don't chain through this exit. Reserved for later phases.

    oneWay: boolean

    Intentionally one-way. Mutual-exit verification skips this Exit when checking that the destination has a back-pointing inverse.

    source: Stuff & Container
    stuffId: string

    Runtime ID for this object (generated using nanoid). This is NOT the MongoDB _id - it's a runtime identifier.

    subscribableFields: SubscribableFieldDescriptor[] = ...

    Universal live-query subscribable fields — fields every Stuff exposes regardless of mixin composition. Currently just displayName, a derived render that delegates to Stuff.getPresentation (Named's name or Visible's shortDescription, falling through to the baked-in 'something').

    Declared here rather than in a substrate-private synthetic table because every Stuff genuinely owns the concept — there is no "what if this Stuff has no displayable identity?" case. The descriptor uses dependsOnFields to declare the leaf source fields it depends on (name, shortDescription); the substrate installs precise (FieldChangedEvent, 'field', dep) index entries automatically. Shadow lifecycle support rides on ShadowChangedEvent in changes (declared-but-unfired until the shadow subsystem wires it).

    Mixin layers above Stuff add their own subscribableFields for mixin-owned state; the substrate's prototype-chain walk hasOwnProperty-checks at every level and unions the descriptors.

    Future universal renders (pronoun, articleName, etc.) land here too. Mixin-gated renders go on the mixin that owns the gate.

    Accessors

    • get destination(): Stuff & Container

      Host-internal getter.

      Resolution order:

      1. _destination (live ref) — present for Pattern B and for Pattern-C-with-direct-live-ref constructor calls.
      2. _destinationPath resolved via StuffApi.findByTemplatePath — re-resolved every call, no cache.

      Throws if no destination is set, or if the path is set but the target zone isn't loaded yet. Callers in the latter case must await exit.resolveDestination() to fault in the zone.

      Returns Stuff & Container

    • set destination(value: Stuff & Container): void

      Setter accepts a live Container. Stamps _destinationPath from the target's templatePath if available (Pattern C), else stores the ref in _destination (Pattern B within-session). NEVER caches a previous path-resolution into the live-ref slot — that was the rule the ref-shapes design dropped.

      Both slots are written symmetrically: whichever branch the new destination takes, the other slot is cleared so the getter's resolution order can never see stale conflicting state.

      Parameters

      Returns void

    Methods

    • True iff a mode named modeName is admitted by this exit. Resolution:

      • Empty media → legacy default; admits only 'walk'. Preserves pre-refactor behavior for exits constructed without an explicit medium set.
      • Non-empty media → resolve the mode singleton via LocomotionApi.modeOf(modeName). If unloaded, reject (matches the strict-resolution contract elsewhere in the substrate). If resolved, admit iff the mode's getMedium() is in media.

      Passthrough modes (ride / drive) have medium === null; they're never admitted by an exit's media list directly — the conveyance host's mode is what gets gated. Player flow always routes through LocomotionControllerBase, which substitutes the host's mode at the exit-gate call site (see LocomotionControllerBase.execute).

      Parameters

      • modeName: string

      Returns boolean

    • Can mover traverse this exit right now?

      Returns { ok: false, reason, gate } on the first failing gate (blocked / closed door / mode rejection); { ok: true } otherwise.

      mode is the short name of the actor's intended LocomotionMode (e.g. 'walk', 'climb'). When supplied, this method consults allowsMode(mode). When omitted (pre-locomotion callers, admin tools), the mode gate is skipped — backcompat-additive.

      Parameters

      Returns TraversalGuard

    • Destroy this object.

      Locked down by @CallSecurity(ApiOnly) — only callers under mud/api/ (in practice, StuffApi.destruct) may invoke it. @Unshadowable because the unregistration path must always run; a shadow that intercepts and skips it would leak the object into the registry forever. @Final because subclass overrides would defeat the same invariant — the loader hook throws FinalViolationError at import time on any subclass redefinition.

      Subclass cleanup belongs on the optional onDestruct() witness (consulted by StuffApi.destruct while the target is still live); refusal logic belongs on canDestruct(). This terminal destroy() is the unshadowable mark-and-unregister step only.

      Returns void

    • Return the destination's templatePath. Returns null only when the Exit was constructed with a live-ref destination that itself has no templatePath (the within-session Pattern B case). Used by the mutual-exit verifier to identify the destination by path.

      Returns string | null

    • Self-presentation — the casual-register render string for this object, the answer to "what does this Stuff call itself?" Three- step resolution:

      1. Named.name if present and non-empty — the object's proper name ("Alice", "Excalibur", "Town Square").
      2. Visible.shortDescription if present and non-empty — the object's visual identity ("a heavy oak door").
      3. The baked-in fallback (DEFAULT_PRESENTATION).

      For a Globbable stack (quantity !== 1) the count folds in as an affix — "30 coins" — pluralized via GrammarApi.pluralize (which honors host-side getPluralForm() overrides for irregulars). Named takes precedence over Visible so a Named-with-description renders by its proper name; code that needs the formal register calls getFullName() when typed as Named.

      Viewer-blind by design. This is the shared baseline every Stuff exposes; the viewer-aware naming step (recognition / identification — see docs/subsystems/belief.md) composes on top of it. Left shadowable (NOT @Final) so masking / disguise effects can override the rendered identity via a method shadow.

      Returns string

    • Build the composable Mml fragment for this object's display name — the Mml sibling of getPresentation. Mml.ref (and so every <item> / <name> / … identity tag) renders this, not a raw string, so a name joins the compose chain as a fragment like everything else. The label is the already-resolved, viewer-aware name (recognition runs in the render layer and hands it in).

      Return null for the plain default — Mml.ref then wraps the label in Mml.text, which escapes it exactly once, so player-authored names / status decoration are safe by construction and the fragment is never re-escaped downstream. Override to build a richer fragment (a TPA terminal wraps its name in <color> to tint by state). The plain-string getPresentation stays the surface for non-prose consumers (logs, context.note, MQL scalars).

      Parameters

      • _label: string

      Returns Mml | null

    • Read seam. Instance method, but unwraps via ProxyApi.unwrap before reaching the # slot — this inside an instance method called through the proxy is the proxy, and the # slot lives on the raw target.

      Returns string | null

    • Check if this object has been destroyed.

      @Unshadowable: the destroyed-state read is a framework invariant — any shadow that lied about it would let consumers touch a torn-down Stuff. @Final: subclasses overriding this would defeat the same invariant; the loader hook throws FinalViolationError at import time on any subclass that redefines it.

      Returns boolean

    • Cleanup hook fired by StuffApi.destruct(this).

      • Clears the inverse back-pointer on the paired Exit so neighbors don't retain references to a dead instance.
      • Removes this Exit from the attached Door's attachedTo set (Phase 4 wires that field — pre-Phase-4 the lookup is a no-op).
      • Drops the local door reference.

      Returns void

    • Resolve the destination, awaiting a singleton clone when the target zone hasn't been loaded yet. Use this from async paths (MobileMixin.traverse) that need to fault in the zone before the cross-boundary move.

      No runtime cache — every call re-reads via StuffApi.singleton(path) (or returns the within-session live ref if one was supplied via setDestination). The cache slot the ref-shapes design dropped used to silently turn path resolutions into stale Pattern-B refs across hot-reload churn.

      Returns Promise<Stuff & Container>

    • Stamp this Stuff's templatePath and re-key the byTemplatePath index so future findByTemplatePath lookups see the new path. No-op when path matches the current value.

      Locked down by @CallSecurity(ApiOnly) because flipping a Stuff's identity post-clone would break FromTemplate policies and any caller-side caching of template-path identity. @Final @Unshadowable because the index update has to run for every successful set — a subclass override that forgot the index call (or a shadow that intercepted) would silently desync byTemplatePath.

      Unwraps via ProxyApi.unwrap so the #-slot access lands on the raw target (see comment on #templatePath above).

      Parameters

      • path: string

      Returns void

    • Set the spatial zone. Gated by FromSpatialZone — only the SpatialZone class and its subclasses (CartesianZone, SphericalZone) may call this through the proxy. The addLocation / removeLocation chokepoints on the zone side are the legitimate callers; everyone else is rejected.

      Clone-time seeding from StuffApi.#cloneInner doesn't go through this method — it uses the caller-allowlisted _stampZone seam below.

      @Final @Unshadowable because the index of substrate invariants that consult getZone() (containment's cross-zone gate, Mobile.traverse, MQL scope walks) trusts the slot's value; a subclass override or shadow that lied about it could break those invariants. No legitimate subclass needs to extend this anyway — the only legitimate write paths are the SpatialZone chokepoints and clone-time.

      Parameters

      Returns void

    • Refresh the recency timestamp to now. Timestamp-fixed (no caller-supplied value). Called on the raw target by the security gate on every successful dispatch (Phase 2) and by the residency presence walk.

      Returns void