Saxonberg Server API
    Preparing search index...

    Hierarchy

    • CocktailShakerBase
      • default
    Index

    Constructors

    Properties

    _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.

    _materialPath: string | null = null

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

    _restingOn: Stuff & Surfaced | null = null

    Runtime-only auxiliary support pointer — Pattern B live ref. Holds a direct reference to the supporting Surfaced host (null when no support). NOT in persistentFields; resets to null on hydrate. R2.3 self-heal in getRestingOn clears the slot if the supporter has been destructed.

    Pattern B chosen over Pattern A templatePath stamping because non-singleton surfaces (e.g., multiple identical tables in a dining hall) can't be addressed unambiguously by templatePath. The cross-restart loss is small — items reappear in their container, just without the on-surface precision.

    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).

    capabilities: string[] = []

    The capabilities this tool offers (validated against the vocabulary).

    details: DetailMap = ...

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

    environment: Stuff & Container | null = null

    Live reference to the container. NOT a persistent field — cross-Stuff references would round-trip badly through the Hydrator's reflection. The container relationship is rebuilt at clone time via the applyContainer instruction-field path (see static instructionFields above) or by direct ContainmentApi.move calls after hydration.

    Auxiliary restingOn is different — it's a Pattern A path-string (_restingOnPath) that DOES persist; see static persistentFields below.

    illustration: string | null = null
    longDescription: string = ''
    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 = "ManualBuildMixin"
    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: { mass: string } = ...

    Field-marshaller binding. mass round-trips via the kg-bound QuantityMarshaller; the runtime accessor pair stays strict on Quantity<'kg'>. Authoring-shape coercion (mass: heavy, mass: "12000 g", bare numeric) lives in the marshaller's fromStored and only runs on the persistence path.

    instructionFields: string[] = ...

    Instruction-field applier roster. details is consumed by applyDetails (Phase 2 of PersistentHydrator). The declarative YAML shape (a plain object keyed by detail name) differs from the runtime Map<DetailId, Detail> shape, so the applier sits between them — it owns the conversion. The applier runs AFTER the persistent bracket-assign in Phase 1 (which would otherwise leave this.details as a plain object and break getDetailEntries); the applier resets the Map up front to undo that.

    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[] = ...

    Persistent fields declared by this mixin. Used by PersistApi for automatic synchronization.

    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

    • Privileged setter for the auxiliary restingOn pointer. Reachable only from ContainmentApi.move / ContainmentApi.placeOn. Pass null to clear.

      Stores the supporting Surfaced ref directly (Pattern B); runtime-only — see the field declaration's JSDoc for the persistence rationale.

      Parameters

      Returns void

    • Phase 2 applier — see the interface docstring for semantics. Compare-and-move idempotency: no-op when the current container's templatePath matches the declared path; otherwise resolve the target via StuffApi.singleton and ContainmentApi.move into it. The singleton-target invariant is enforced at template-save time by TemplateApi.validateSingletonContainerTarget.

      Parameters

      • path: string

      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

    • 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 the current container.

      R2.3 self-heal: if environment points at a destroyed Container (a path bypassed the eager evacuation in Container.cleanupOnDestruct), clear the slot and return null. Cheap one-liner backstop for S1 / S8.

      Returns Stuff & Container | null

    • 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

    • 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

    • 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

    • Resolve the auxiliary restingOn pointer. Pattern B live ref; R2.3 self-heal clears the slot if the supporting surface has been destructed since the last set.

      Returns null when no support OR the supporter has been destructed. The caller can't tell the two apart from the return value; that's deliberate — absence of support is the same observable as a stale ref.

      Returns Stuff & Surfaced | null

    • Walk the container chain to the topmost non-null environment. Returns null when already at the root.

      Containment is acyclic by construction (a Container can't contain its own ancestor — setContainer's atomic update is the chokepoint), so the loop is bounded by the depth of the world's nesting.

      Returns Stuff & Container | 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

    • 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

    • Terminal onDestruct no-op. Exists so subclasses and mixins overriding onDestruct can call super.onDestruct() without the cast-to-optional-callable dance — the chain is guaranteed to bottom out here. StuffApi.destruct invokes the hook via the optional-method dispatcher in api/stuff.ts; that path still works (always finds a function on the prototype).

      Override (not extend with super) at any layer that wants cleanup; chain to super.onDestruct() from the override so intermediate layers in a mixin chain run too.

      Returns void

      Invoked by StuffApi.destruct (and forceDestruct) while the target is still live, after canDestruct passes and before shadow-detach + destroy(). Witness — the return value is ignored (it cannot veto; canDestruct is the veto seam). Override to release resources/listeners and chain super.onDestruct() so mixin layers run.

    • Record the verbatim command source of a completed manual step (the demonstration-capture trail). A scripted build records nothing (its dispatched commands carry no typed source), so only a hand-typed build accumulates a transcript — the natural manual-vs-scripted discriminator. Empty/blank sources are ignored.

      Parameters

      • source: string

      Returns void

    • State-mutation chokepoint. Reachable only from ContainmentApi.move; cross-Container contents mutation must not be subclass-extensible (@Final) or shadow-bypassable (@Unshadowable).

      Atomic across three updates: detach from the old container, attach to the new, update the field. null argument is the detach case; the policy rejects calls from anywhere other than ContainmentApi, so setContainer(null) outside the Api throws — the legitimate detach is ContainmentApi.move(item, null).

      Parameters

      Returns 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

    • 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

    • Decrement condition by amount (clamped at 0). The wear-on-use seam.

      Parameters

      • amount: number = WEAR_PER_USE

      Returns void

    • Framework cleanup (R2.4 collection-symmetric). When a Containable destructs, unhook it from its container's contents set via the canonical chokepoint so onMoved / onContainableRemoved witnesses fire. Discovered by the dispatcher in StuffApi.#destructCore via the MixinApi.queryMixins walk + own-static filter.

      The Container-side cleanup (most-derived) for a Container+Containable composition fires BEFORE this — it evacuates contents while _container is still set, then this hook completes the unhook for the destructing item's own membership in its outer container.

      Parameters

      Returns void