Saxonberg Server API
    Preparing search index...

    Hierarchy

    • CartesianLocationBase
      • default
    Index

    Constructors

    Properties

    Accessors

    Methods

    Constructors

    • Constructor - generates unique runtime ID.

      Subclass constructors should call super() and then initialize their fields. Use field initializers for default values where possible.

      IMPORTANT: Direct new SomeStuff() is rejected — every Stuff must be created via StuffApi.create(() => new YourClass()) or StuffApi.clone(...). The construction sentinel above guarantees this; raw new outside the Api layer throws here.

      Constructor-body method calls bypass the Proxy (the Proxy is installed AFTER the constructor returns). Initialize fields here; do NOT invoke methods that carry

      Returns default

      from inside a constructor body.

    Properties

    _address: string | null = null

    Declared address path in the namespace, or null. Sparse — null is the common case and the resolve-walk falls through it to a containment ancestor or the spatial zone.

    _atmosphere: string | null = null
    _biomePath: string | null = null
    _coords: { x: number; y: number; z: number } | null = null

    Persistent declarative-content field. _coords holds the YAML-shape input {x, y, z}; the runtime tuple lives on CartesianCoordinatesMixin.coordinates ([x, y, z]). The setter bridges them — see setCoords.

    Per declarative-content-slate § coords on CartesianLocation, feedback_property_vs_instruction_fields (property field shape: storage IS the value), and ref-shapes.md § Field naming (backing slot is _coords, public surface is getCoords / setCoords).

    _detailAtmospheres: Record<string, string> = {}
    _detailGravities: Record<string, Quantity<"m/s²">> = {}
    _detailHumidities: Record<string, Quantity<"%">> = {}
    _detailMaterialPaths: Record<string, string> = {}

    Per-Detail Material overrides — flat map from detailKey to the Material's templatePath. Stored as a plain Record (not a Map) so default JSON serialization handles it without a marshaller.

    _detailPressures: Record<string, Quantity<"Pa">> = {}
    _detailTemperatures: Record<string, Quantity<"K">> = {}
    _detailWinds: Record<string, Quantity<"m/s">> = {}
    _gravity: Quantity<"m/s²"> | null = null
    _humidity: Quantity<"%"> | null = null
    _materialPath: string | null = null

    Path to the bulk default Material singleton. Resolved lazily on each getMaterial() call so HMR replacement is observed immediately.

    _pressure: Quantity<"Pa"> | null = null
    _temperature: Quantity<"K"> | null = null
    _wind: Quantity<"m/s"> | null = null
    autoDeriveKeywords: boolean = true

    Opt-out for auto-deriving keywords from the host's display name (via NamedMixin) and short description (via VisibleMixin). Default true — most hosts want "a brass thermometer" to fold into ['brass', 'thermometer'] automatically. Set to false in template data when you want hand-curated keywords only (e.g., a "scroll of resurrection" where you want just 'scroll', not ['scroll', 'of', 'resurrection'] — though "of" would be dropped as a stop word anyway, "resurrection" wouldn't).

    contents: Set<Stuff & Containable> = ...

    The contained items. Read access goes through getContents(); mutation goes through addContainable / removeContainable, which only Containable.setContainer may legitimately invoke.

    coordinates: [number, number, number] = ...
    details: DetailMap = ...

    Hierarchical detail map. Host-internal storage; external callers go through getDetail / setDetail / removeDetail.

    exits: Map<string, default> = ...

    Explicit exit map. Derived exits (cartesian adjacency, vessel 'out') are NOT stored here — they are synthesized lazily by the zone and by ExitableVessel.getExit() respectively.

    Host-internal storage; external callers use getExits() / getExit(direction).

    fixtureSlots: Map<string, Stuff & Adornment> = ...

    Fixtures keyed by slot name. Single source of truth — the Slotted base's slots Map stays empty for Adornable hosts because occupy / vacate on Adornable are routed through addFixture / removeFixture.

    Runtime-only — fixtures are reconstructed by seed clone hooks (BoundaryApi.attachExistingBoundary).

    illustration: string | null = null
    longDescription: string = ''
    nextFixtureIndex: number = 1

    Counter for synthetic slot names. Instance-local, runtime-only.

    primaryKeyword?: string

    Authored primary keyword. When set, getPrimaryKeyword() returns this value (after fail-soft validation against the live keyword pool). Persistent — author-set via template data:.

    Hydrator routes through setPrimaryKeyword (the Phase 1 dispatch prefers a set<Field> method), so an authored-but-invalid value in a template is logged + dropped at clone time rather than silently sitting in the slot waiting to confuse a renderer.

    shortDescription: string = ''
    stuffId: string

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

    _mixinName: string = 'SingletonMixin'
    commandContributions: CommandContributions = ...

    Visible is target-shape only — no verb contributions. See the mixin docstring for why look.yaml belongs on Perceiver's self bucket, not on Visible's target-side buckets.

    fieldMarshallers: {
        _gravity: string;
        _humidity: string;
        _pressure: string;
        _temperature: string;
        _wind: string;
    } & { mass: string } = ...

    Field-marshaller bindings for the five Quantity-typed bulk fields. The per-detail maps round-trip via standard JSON (Quantity.toJSON / fromJSON) — Record<string, Quantity<U>> serializes natively without a map marshaller.

    instructionFields: string[] = ...

    Instruction field consumed by applyPopulates. The YAML data is an array of templatePath strings; Phase 2 dispatches by source-template singleton-shape and moves the resulting instance into self.

    markupAugmenters: MarkupAugmenter[] = ...

    Markup-augmenter contribution. The wrapper-style augmenter narrows the host to Detailed at runtime, then runs the existing wrapDetailKeywords regex pass. Picked up by VisibleMixin.getMarkupLong(viewer) via the prototype-chain walker — non-Detailed hosts never see this augmenter; hosts with Detailed-but-empty detail maps no-op cheaply inside the helper.

    persistentFields: string[] = ...
    subscribableFields: SubscribableFieldDescriptor[] = ...

    Live-query subscribable fields. The descriptor's dependsOnFields defaults to ['details'] (descriptor name = source field name), so FieldChangedEvent { field: 'details' } from setDetail / removeDetail triggers re-projection automatically. The ShadowChangedEvent entry covers future visible-detail shadows that override projected entries without firing a field change.

    One descriptor carries both projection layers: read enumerates the alias-grouped top-level entries (flat mode); perDetailRead extracts a single entry's slice for focus- mode subscriptions.

    Accessors

    • get keywords(): string[]

      Host-internal accessor pair (Pattern D). External callers go through getKeywords() / setKeywords(). The private setter still fires when the Hydrator bracket-assigns target['keywords'] = data['keywords'] — bracket access bypasses TS visibility, so the normalization invariant runs during hydration.

      The getter returns the derived keyword pool: authored keywords plus, when autoDeriveKeywords is true, tokenized words from the host's display name (NamedMixin) and short description (VisibleMixin). Authored entries always lead so an exact-keyword match outranks a tokenized match (see scope-walk.scoreCandidate). Internal callers that need the raw authored set go through _keywords directly.

      Returns string[]

    • set keywords(value: string[]): void

      Parameters

      • value: string[]

      Returns void

    Methods

    • Install a forward/back exit pair in one call. Both sides share the same Door reference when one is supplied, so opening from either side flips a single state.

      Reciprocity: the opposite direction is inferred from NavigationApi.invertDirection(direction) for cardinal directions. For non-cardinal labels ('office', 'portal', vessel-specific names) the caller MUST supply opts.opposite — there is no structural inverse to infer. Passing opts.opposite for a cardinal direction overrides the inferred inverse.

      Parameters

      Returns Promise<void>

    • State-mutation primitive. Locked down — only callable from Containable.setContainer. Use ContainmentApi.move(item, container) from application code.

      Fires FieldChangedEvent { field: 'contents' } after a real addition so the MQL subscription substrate's dependency index picks up containment-shape changes for the contents descriptor. The substrate matches on (KIND, 'field', 'contents') only — oldValue / newValue are inspected by the diff pass via re-projection of the host, not by the index, so the count delta carried here is informational (debugging / future coarse-grain optimizations) rather than load-bearing.

      Parameters

      Returns void

    • Tightened cardinal rule per zone-architecture-slate § The cardinal-only-intra-zone exit invariant:

      • Cardinal direction (n/s/e/w/diagonals/up/down): always allowed.
      • Non-cardinal direction: only allowed when the destination's templatePath resolves to a different zone than the source's. The check is path-based via ZoneApi.resolveZoneForPath (walks template ancestry in Mongo, no need to load the destination room as a Stuff). Zones materialize lazily — first reference triggers a clone, subsequent references hit the singleton cache.

      Result: a freely-authored CartesianZone can have semantic exits (portal, office) that cross into a SphericalZone or another CartesianZone, but never inside its own grid.

      The override is async because zone resolution may clone the zone Stuff on first reference. The base interface already carries Promise<boolean> so the override is type-safe.

      Parameters

      Returns Promise<boolean>

    • Phase 2 applier — clone each adornment template and attach it as a fixture. Mirrors applyPopulates, minus the singleton dispatch: fixtures are per-instance, so every entry is cloned fresh. A template that doesn't compose AdornmentMixin is a configuration error (it can't be a fixture) and throws, naming the path.

      Parameters

      Returns Promise<void>

    • Declarative applier — wires the template YAML's details: map into the runtime Map<DetailId, Detail>. Phase 1 of the hydrator bracket-assigned the plain YAML object onto this.details, breaking the Map shape; the first thing we do is reset it. Then walk the entries — each shaped either legacy ({ keywords?, description: string }) or new ({ keywords?, vision?, hearing?, smell?, touch?, taste? }) — and call setDetail for each. Aliases are [key, ...keywords] with duplicates squashed.

      Mixed-shape entries (legacy description AND any per-sense slot key in the same entry) throw — authors pick one shape per entry. Malformed entries (no recognized shape, non-object payload) are skipped with a warn. Nested children via the details: sub-key are deferred to a future revision; v1 is flat.

      Parameters

      • data: Record<string, unknown>

      Returns void

    • Declarative-content applier. The instruction field is consumed here: each ExitInstruction is translated into an explicit Exit (or addBidirectionalExit for cardinal / explicit-bidirectional entries). Destinations and doors lazy-clone via StuffApi.singleton, so a depended-on Location is materialized on first need; further singleton(x) calls during a cascade hit the registered proxy and short-circuit cycle scenarios.

      Per-direction idempotency: a matching existing exit (same destination, same door) is a no-op; a mismatch throws with a diagnostic naming both seed paths.

      Parameters

      Returns Promise<void>

    • Phase 2 applier. See class docstring for dispatch semantics.

      Class resolution goes through StuffApi.loadClassByPath — the existing public class-loading Api surface. The Template lookup is a separate Template.findByPath call so we have tpl.class to feed into loadClassByPath.

      Parameters

      Returns Promise<void>

    • 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

    • Get a detail's per-sense slot value.

      Overload-friendly runtime dispatch:

      • getDetail(id) — returns vision slot.
      • getDetail(id, sense) where sense is a SenseChannel literal ('vision' | 'hearing' | 'smell' | 'touch' | 'taste') — returns that slot.
      • getDetail(id, sense, parent) — sense + nested parent.
      • getDetail(id, parent) where the second arg is NOT a known sense channel — treats it as legacy parent and returns the vision slot at the nested path.

      Parameters

      • id: string
      • OptionalsenseOrParent: string
      • Optionalparent: string

      Returns string | null

    • Alias-grouped enumeration of top-level (or parent-scoped) details. Walks the DetailMap at the requested level grouping keys by Detail-object identity, so aliases (multiple keys pointing at the same Detail object) bundle into one entry. hasChildren reflects whether the entry's Detail has its own nested DetailMap.

      Parameters

      • Optionalparent: string

      Returns DetailEntry[]

    • Look up the single alias-grouped entry whose Detail covers key. Supports the same dotted-path resolution that getDetail uses. Returns null when no detail exists at the requested key.

      Parameters

      • key: string

      Returns DetailEntry | null

    • Merged lookup:

      1. explicit → wins
      2. (subclass hook — ExitableVessel overrides for 'out')
      3. zone-derived (only CartesianZone returns anything)
      4. undefined

      Parameters

      • direction: string

      Returns default | undefined

    • Read the recency timestamp. Read by the residency sweep — which calls it on the raw target (via RAW_TARGET) so the sweep's own introspection never counts as a touch.

      Returns number

    • Affordance-annotated long description — see the interface docstring for the augmenter pipeline contract. Calls Mml.augment with the host (this), the supplied viewer, and the per-call opts (the senses build threads opts.filter through here for verb-specific sense filtering). Every contributing mixin's augmenters run in parent-first → child-last order.

      Parameters

      Returns string

    • Exits displayed by look — explicit ∪ derived, filtered by !hidden.

      Zones that synthesize cardinal-derived exits (CartesianZone) opt in via Zone.hasDerivedAdjacency(); zones without adjacency derivation (SphericalZone) skip the iteration entirely so we don't waste 10 always-undefined deriveExit calls per query.

      Returns default[]

    • 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 the primary keyword for this Stuff. Returns the authored value when it appears in the current derived keyword pool; otherwise the last derived-pool entry; otherwise undefined.

      Last-pool-entry (rather than first) is the better default for English modifier-noun phrases. Derived-pool ordering is authored keywords first, then tokenized name (NamedMixin), then tokenized shortDescription (VisibleMixin). For a Named "Oak Door" the tokens land in order ['oak', 'door']; for 'a brass thermometer' the tokens land ['brass', 'thermometer']. In both cases the head noun is the trailing token — what a player would naturally type to refer to the thing — and what look <X> click-affordances should send.

      Authors who need a non-trailing keyword pin it explicitly via setPrimaryKeyword(...). The substrate default is just a sensible last-resort.

      Intentionally does NOT call setPrimaryKeyword from the getter — the setter is a separate event surface from rendering.

      Returns string | undefined

    • Effective receiving-surface area in m² used by VisionModality.lightAt to convert accumulated lumens to lux: the walk divides the total flux at this room by getSizeScale(). Derived from the linear cell extent by squaring it — cellSize: 3 → 9 m² floor area. Larger rooms read dimmer for the same total flux. The fallback covers transient test state where the room hasn't been added to a zone yet.

      Returns number

    • Resolve the temperature at this scope (optionally narrowed by a detailKey). Routes through BiomeApi.resolveTemperatureFor, which walks innermost-container-outward then biome / zone / universe.

      Parameters

      • OptionaldetailKey: string

      Returns Promise<Quantity<"K">>

    • 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

    • Narrowed override: a CartesianLocation lives in a CartesianZone by CartesianZone.addLocation's rejection of non-Cartesian locations. The cast happens once here, at the boundary; every caller within (or with a typed reference to) CartesianLocation gets the narrowed type for free. If a CartesianLocation ever landed in a non-Cartesian zone, getCellSize would be undefined and any optional-call would short-circuit — defensive but documented.

      Returns default | null

    • Membership test for a single detail id at the given level. Returns true iff the detail exists and ANY sense slot is populated (cheaper than walking the slot map externally).

      Parameters

      • id: string
      • Optionalparent: string

      Returns boolean

    • 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

    • Mutual-exit verification: wires inverse pointers for any outbound exit whose destination is already loaded, or marks the exit blocked if the destination's topology doesn't match. Defers anything whose destination hasn't been loaded yet — the destination's own load (or the next traversal) will rerun the check. See ExitableMixin.verifyOutboundExits.

      Parameters

      • Optional_context: unknown

      Returns Promise<void>

    • Setter with side effect: stores the value AND registers this location with its resolved CartesianZone via addLocation(this, x, y, z). Idempotent on the happy path (same coords → no-op); throws with a conflict diagnostic when already at different coords. The zone resolution uses getZone() — at hydrate time, Stuff.zone has been stamped (see lifecycle: zone stamp runs before hydrate).

      Pattern: setter with side effects, parallel to SphericalLocation.setFocus and Window.setAttachedHosts. Per declarative-content-slate § coords on CartesianLocation.

      Parameters

      • value: { x: number; y: number; z: number }

      Returns Promise<void>

    • Set detail(s) — supports multiple IDs (aliases). Accepts both the legacy string-description shape and the new per-sense slot map shape: setDetail(ids, "A brass handle.") → populates vision slot. setDetail(ids, { vision: "...", touch: "..." }) → per-slot.

      All IDs in a single call share one Detail object (alias semantics). Separate calls always produce separate Detail objects, even when slot values match.

      Per-field invariants: the slot-map shape must populate at least one channel (empty maps throw); empty-string slot values are rejected. A legacy string-description shape accepts empty string (preserves prior behavior for tests / edge cases).

      Parameters

      • ids: string[]
      • descriptionOrSlots: string | Partial<Record<SenseChannel, string>>
      • Optionalparent: string

      Returns number

    • Replace the keyword list. Equivalent to this.keywords = keywords, kept for symmetry with the addKeyword/removeKeyword API.

      Parameters

      • keywords: string[]

      Returns void

    • Strict on Quantity<'kg'>. Callers holding a raw number wrap via Quantity.of(n, 'kg') at the call site; tag / alt-unit authoring is the marshaller's job, not a runtime API concern.

      Parameters

      Returns void

    • Author-set the primary keyword. Stores the normalized value unconditionally; pool-membership is a cross-field invariant (the pool depends on shortDescription via VisibleMixin and name via NamedMixin) and the Hydrator's Phase 1 dispatch makes no ordering guarantee across mixins. Validating in the setter would (and did) silently drop authored values when this mixin's setter ran before the others contributing to the pool.

      The getter (getPrimaryKeyword) does the lookup: if the stored value is in the current pool, return it; otherwise fall back to pool[0]. That keeps authored intent honored regardless of hydration order, and a runtime caller passing a bogus value gets the same silent override behavior the getter already implements for any out-of-pool entry.

      Passing undefined clears the explicit override; subsequent getPrimaryKeyword() calls fall back to the derived-pool head.

      Parameters

      • value: string | undefined

      Returns void

    • Override the temperature at the room/vessel scope or at a specific detailKey. null clears the override (the next read falls through to the chain).

      Parameters

      • value: Quantity<"K"> | null
      • OptionaldetailKey: string

      Returns void

    • 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

    • Mutual-exit invariant check, run at Location load (via postRegister) and on traversal as a fallback. Walks ONLY the _pendingVerify set — exits the addExit-time triage flagged as "could be wired but isn't yet."

      For each pending exit:

      • If it has settled by some other path (its inverse was wired by the destination's own verifier, it became oneWay / blocked, etc.), evict from the pending set and move on.
      • Else attempt to resolve: if the destination is now loaded and a matching back-exit exists, wire the inverse pointer pair. If the destination is loaded but the topology is wrong, mark the exit blocked = true with a logged warning. Either way, evict.
      • Else (destination still unloaded, or back-exit's destination unloaded), leave in the pending set for a future pass.

      Idempotent. Zone-derived cartesian exits are synthesized per-call and inherently mutual by grid adjacency — they aren't tracked.

      Returns void