ReadonlystuffRuntime ID for this object (generated using nanoid). This is NOT the MongoDB _id - it's a runtime identifier.
Static_StaticsubscribableUniversal 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.
Every known recipe.
Singleton refusal (mirrors TopicCatalogue / SoulCatalogue).
Residency veto - a load-bearing process-lifetime singleton is never culled by the self-eviction sweep.
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.
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.
Self-presentation — the casual-register render string for this object, the answer to "what does this Stuff call itself?" Three- step resolution:
Named.name if present and non-empty — the object's
proper name ("Alice", "Excalibur", "Town Square").Visible.shortDescription if present and non-empty —
the object's visual identity ("a heavy oak door").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.
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).
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.
Get the spatial zone. Unwraps via RAW_TARGET because the
# slot lives on the raw target only and this inside an
instance method called through the proxy is the proxy.
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.
Whether recipeId is known.
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.
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.
Optional_context: unknownStamp 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).
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.
Get a string representation of this idea (for debugging). Subclasses should override to provide more specific information.
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.
(Re)build the cache + keyword index from the recipes collection.
Constructor - calls parent Stuff constructor.