ProtectedappearanceFree-prose appearance phrase for the substance as it reads in a
holder — e.g. 'dark, steaming coffee', 'clear water'. The bulk
substrate composes this into a holder's description (look thermos)
and a surface puddle's line (a puddle of water). Plain property
field; storage IS the value.
Material composes PerceptibleMixin for its keyword pool
(so drink coffee resolves a holder by its bulk material) but NOT
Visible / Named — substance identity stays out of the
perception-target machinery (no Sensor, scene, or light behavior),
so material keywords never leak into room scope. Keywords are
purely authored; appearance is the rendered phrase.
ProtectedatomicAtomic number (Z). Element-only.
ProtectedautoOpt-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).
ProtectedbiologicalSource-species link for biological materials. null for non-
biological materials and for biological materials where the source
species isn't modeled. The species side carries
_defaultMaterialPath pointing back here — the link is
bidirectional per slate.
ProtectedcompositionConstituent breakdown for mixtures / alloys / composite materials.
Pure elements have an empty list. Each entry's materialPath
resolves lazily through StuffApi.findByTemplatePath at query
time; same shape as _speciesPath / _materialPath cross-refs.
ProtectededibilityWhether this material can be eaten. v1 has no consumer.
ProtectedformulaChemical formula (e.g. 'H2O'). Compound-only.
ProtectednameDisplay name (e.g. 'iron', 'oak', 'fruit-flesh').
ProtectednutrientInspectable nutrient amounts (tag → mg per serving) — the
education-by-reference profile examine <food> surfaces. Parallel
to nutrients: the tags drive routing, these amounts are
data + display only (no body-side machinery). A flat
Record<string, number> → default-Hydrator round-trip (the
reserves precedent), no marshaller.
ProtectednutrientsTagged nutrient categories ('protein', 'water', 'sugar') —
the tags that drive metabolism's macro routing (kept as bare
tags for back-compat with existing seeds). The inspectable
per-amount profile lives in nutrientAmounts.
Protected OptionalprimaryAuthored 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.
ProtectedsavedPer-prop marshaller bindings. Maps prop name → marshaller
templatePath. Persistent and arrives alongside savedProps
during hydration. setProp / getProp consult this map to
decide whether to invoke toStored / fromStored for a
given value. Props without a binding here round-trip raw, the
way scalar props always have.
Auto-initialized to an empty object; populated by
initProp(prop, { marshaller: '/path' }).
Protected OptionalsavedPersistent properties — STORED SHAPE. Host-internal storage;
setProp applies marshaller.toStored before writing here
when a marshaller is bound for the prop, and getProp
applies marshaller.fromStored on read so external callers
see the runtime type. The hydrator's bracket-assign deposits
the raw record directly without per-key conversion — the
marshaller binding lives on savedPropMarshallers and runs
lazily at access time.
ReadonlystuffRuntime ID for this object (generated using nanoid). This is NOT the MongoDB _id - it's a runtime identifier.
ProtectedsymbolElement symbol (e.g. 'Fe', 'C', 'U'). Element-only.
ProtectedtagsFree-form classification tags. See class header for layer-1 description; vocabulary intentionally not centrally registered.
ProtectedtoxicityPer-consumable toxin doses — {type, amount}[]. type is the toxin
(and its Condition key, e.g. 'alcohol'); amount is the dose
per serving (mg for solids / derived for liquids). The per-body rate
params (absorption, clearance, potency, severity bands) live on the
toxin's Condition seed, NOT here — the same amount-vs-rate split as
nutrients. A list of flat objects → default-Hydrator round-trip (the
composition precedent), no marshaller.
ProtectedtransientTransient properties (memory only, lost on restart).
Static_StaticfieldField-marshaller bindings. The persistence pipeline routes hydration / save through these marshallers; setters stay strict on the runtime value type.
StaticpersistentStaticsubscribableLive-query projection for primaryKeyword. Lives on the mixin
(not on Stuff) because the field is mixin-gated: only
Perceptible-composed hosts have a keyword pool. The substrate's
prototype-chain walk unions this with Stuff.subscribableFields
at projection time. Non-Perceptible hosts contribute no
primaryKeyword descriptor; the substrate omits the field
from their wire records, same shape quantity uses on
Globbable.
dependsOnFields lists the leaf sources: the authored override
(primaryKeyword) AND the two fields the derived-pool head
folds in (name via NamedMixin, shortDescription via
VisibleMixin). Renaming or re-describing a Perceptible host
re-projects the keyword surface without an explicit setter
call. Shadow-lifecycle support rides on ShadowChangedEvent
for parity with displayName.
ProtecteddensityProtectedkeywordsHost-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.
ProtectedmolarProtectedpropsHost-internal accessor; external callers go through getProps().
ProtectedspecificProtectedthermalConsent seam for the residency (self-eviction) sweep. The sweep
asks each idle object whether it may be culled; the object decides,
reading its own knowledge. Default is cull ({ ok: true }) — a
fresh backing class is reclaimable by default and only becomes
sticky when its author deliberately vetoes, the correct bias for a
leak-plugger. Return { ok: false, reason } to veto.
Vetoes layer on the mixin/class that owns the relevant
relationship, composed via super.canEvict(context) — base Stuff
stays permissive and does not reach into Container/Shadow/Avatar/
Exit knowledge. The relational vetoes derive from the R2.x
ref-cleanup rules: an object in an owned/symmetric live-ref
relationship vetoes while its anchor is alive (see
docs/subsystems/residency.md).
Distinct from canDestruct: an object that permits eviction can
still canDestruct-veto, so the sweep's enforce path tolerates a
DestructError (logs + continues). The sweep calls this on the
raw target (via RAW_TARGET) so asking never counts as a touch.
Check if property exists AND the caller is allowed to see it.
Returns the prop's options when both are true, null otherwise.
Single answer to "does this prop exist and can I read it?":
the Get access op gates visibility, so a denied caller gets
the same null as for a non-existent prop. Lets EventApi.on
disambiguate "no such event / not allowed to subscribe" from
"exists and emitted-but-no-payload-yet" without sentinels.
Change property configuration.
Default access control. Override in subclasses to provide custom access control.
Default implementation allows all operations.
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 substance's appearance phrase (may be empty).
Aggregate chemistry view. Returns null when none of the
chemistry fields are populated; otherwise returns an object
carrying whichever subset is set. For programmatic chemistry
inspection where reading a few fields at once is more
convenient than four individual getters.
Read density. Strict-shape on Quantity<'kg/m³'>; the
QuantityMarshaller absorbed authoring-shape coercion at the
persistence boundary, so callers see only the runtime type.
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.
Read molar mass. Strict on Quantity<'g/mol'>; the
QuantityMarshaller for g/mol absorbs authoring-shape coercion
at the persistence boundary.
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 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.
Get property value (with masks applied).
Lazily auto-initializes prop options when savedProps carries
a hydration-delivered entry but propOptions doesn't yet —
otherwise the value would be unreachable through the public API
(getProp returns null without a config). Auto-init uses
transient: false so the prop's persistence flag matches its
storage location.
Read-only view combining saved + transient properties. Saved
props are returned in their RUNTIME form — marshalled props
pass through marshaller.fromStored on the way out so callers
see Quantity instances and friends, not the raw stored shape.
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.
Initialize a new property with options. Pass marshaller:
for props that hold rich value objects — the binding persists
alongside savedProps, so the same marshaller will run on
future loads of this host.
Optionaloptions: PropOptionsInput<T>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.
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.
Set the substance's appearance phrase.
Bulk-set chemistry. setChemistry(null) clears all four
chemistry fields; passing a partial record sets the named
fields and leaves the others at their current values? No —
the convenience setter REPLACES the whole chemistry block to
keep the holder/component contract symmetric. Use the
individual setters for partial updates.
Set density. Strict on Quantity<'kg/m³'> — authors who hold a
raw number wrap it via Quantity.of(n, 'kg/m³') at the call
site. The QuantityMarshaller handles raw-shape coercion only on
the persistence path; in-process callers commit to the typed
value.
Replace the keyword list. Equivalent to this.keywords = keywords,
kept for symmetry with the addKeyword/removeKeyword API.
Set molar mass. Strict on Quantity<'g/mol'> — wrap raw numbers
via Quantity.of(n, 'g/mol') at the call site.
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.
Set property value (auto-initializes if doesn't exist).
If savedProps already carries an entry for this name (e.g.
delivered by hydration from a seed template), auto-init treats
the prop as transient: false so subsequent writes route back
to savedProps rather than landing in transientProps. Without
this branch, hydrated values would be silently shadowed by the
first set call.
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).
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.
Constructor - calls parent Stuff constructor.