StaticassertOnce-per-class composition validation hook.
Walks the prototype chain calling each level's static
__validateComposition__(ctor) method exactly once per concrete
class. Mixins that want to enforce composition constraints
declare the static; everyone else is a no-op.
Called from StuffApi.register so the check fires the first time
an instance of a given class lands in the registry. Subsequent
registrations of the same class short-circuit on the WeakSet
memo.
The memo is keyed on constructor identity, not class name.
HotReloadApi.reload re-evaluates a module and produces a NEW
class binding (per ModuleApi.stamp's "first-stamp-wins" rule —
same name, fresh identity). The new class is not in the WeakSet,
so the next first-instance-of-class registration re-runs
validation against whatever __validateComposition__ is on the
NEW chain. Old class identities stay memoized — fine, because
nothing creates new instances of a post-HMR-retired class.
Leaf reload required. Reloading a mixin module alone is NOT
enough to pick up a new check. JS class inheritance is bound at
class-definition time: class Coin extends GlobbableMixin(Idea)
captures whatever GlobbableMixin returned at that expression's
evaluation. Reloading Globbable.ts produces a new mixin
function and registers it with HotReloadApi, but Coin's
prototype chain still points at the OLD mixin output. Since
Coin's constructor identity hasn't changed, the WeakSet hit
memoizes the old validation forever.
To rotate the validation: reload the leaf class too. That
re-evaluates its class Coin extends GlobbableMixin(Idea)
expression against the new mixin output, produces a fresh
Coin constructor identity, and the next first-instance triggers
the new check.
No auto-cascade — there's no machinery that reloads leaves when
a mixin reloads. That's intentional: bulk re-instantiation while
a player is mid-action would be jarring. The right tool for
"refresh every Globbable in the world" is an MQL query (e.g.,
world:[mixin.GlobbableMixin]) plus an explicit reload, run by
the dev when they're ready. Forgetting to reload leaves doesn't
create inconsistency — the old check just keeps applying; the
new constraint silently doesn't tighten, but nothing breaks.
Cross-reference: docs/subsystems/mixins.md §Composition
validation, docs/subsystems/hot-reload.md §Composition
validation.
GlobbableMixin — ⊥ Container, ⊥ Singleton,
globIdentityFields ⊂ persistentFields.PerceiverMixin — requires Sensor on the chain. The TS
bound is loose (MixinConstructor); the Perceiver extends Sensor interface relationship narrows the type but doesn't
enforce composition. Without runtime co-composition,
MixinApi.isPerceiver would lie.A bound is cheaper and more specific — see the principle in
docs/subsystems/mixins.md §Composition validation. These
mixins document a constraint, but the constraint is enforced at
compile time and doesn't need the runtime hook:
AdornableMixin — MixinConstructor<Stuff & Container>.WearableMixin / WieldableMixin —
MixinConstructor<Stuff & Slottable & Containable>.MobileMixin — MixinConstructor<Stuff & Containable>.PosturedMixin / MountableMixin / DrivableMixin —
MixinConstructor<Stuff & Slotted>.WorkspaceMixin — MixinConstructor<Stuff & Environment>.Soft pairings documented in JSDoc but not strictly required
(e.g., AdornmentMixin typically composed with Containable so
it can become inventory after detach — but a never-detached
adornment is fine without it) are not modeled here either.
StaticcollectCollect the settings-schema entries declared across a class's
prototype chain — the raw walk over each layer's own static
settings array, one (entry, sourceMixin) pair per declared
entry. Picks up static settings from both mixin layers (the
common case, provenance = _mixinName) AND from substrate
classes whose concept they own (provenance falls back to name,
then <anonymous>).
Sibling of getAllFieldMarshallers — same mixin-static-slot
collection shape. Shared by EnvironmentMixin (whose
collectSchema wrapper layers duplicate-key validation on top)
and ShellApi.resolveSetting's non-Environment fallback. The
duplicate-key throw is intentionally NOT here — that's settings
validation, which belongs in the Environment wrapper.
The class constructor to inspect
Array of (entry, sourceMixin) pairs in chain order
StaticgetActive-mixin substrate (augmentation Wave 1).
Returns the set of mixin constructors currently "active" on
stuff. The substrate splits build-time composition from
runtime activation:
_augmentGated !== true).
Native composition is sufficient — active iff composed._augmentGated === true) are active
ONLY when an installed augment confers them via
AugmentMixin.confers().The walk is lazy in v1 (no cache); installing or removing an augment takes effect on the next call. Future optimization caches the set on the entity, invalidated on slot occupy/release.
The implementation looks at slot occupancy via
SlotApi.getAllOccupants for hosts that compose
SlottedMixin; non-slotted hosts have no augments and active
= composed for everything.
StaticgetGet the field-marshaller registry for a class — the map of persistent-field names → marshaller templatePaths declared on mixins / classes in the prototype chain.
Walks the prototype chain concrete-class-first, so a subclass's
declaration wins over a base mixin's for the same field. The
returned map is keyed by field name; values are templatePath
strings that callers resolve via StuffApi.findByTemplatePath
at use time.
Mirrors the shape of getAllPersistentFields and is the
companion lookup for PersistentHydrator / Document's
marshaller-aware coercion path.
The class constructor to inspect
Map of field name to marshaller templatePath
StaticgetWalk the prototype chain unioning the static globIdentityFields
arrays declared at each level. Deduplicates. Mirrors the shape of
getAllPersistentFields.
A glob's "kind" is defined by the values of these fields plus
templatePath; two globs merge iff their templatePath matches and
every glob-identity field has equal values.
StaticgetWalk the prototype chain unioning the static instructionFields
arrays declared at each level. Deduplicates. Mirrors the shape of
getAllPersistentFields but for instruction fields —
declarations applied to produce/modify derived runtime state via
an applyX method, rather than stored as the value of a property.
Instruction fields are the second half of the property/instruction
split (see feedback_property_vs_instruction_fields). exits on
ExitableMixin is the canonical example: the YAML data is a
Record<string, ExitInstruction> recipe, applied by applyExits to
populate the runtime exits: Map<string, Exit>. There is no
paired getter for the spec; the runtime collection has its own
API (getExit, addExit, …).
PersistentHydrator dispatches in two phases: Phase 1 reads every
entry in getAllPersistentFields and writes via setX (or
bracket-assigns when no setter exists); Phase 2 reads every entry
in getAllInstructionFields and calls applyX. An instruction
field whose applyX method is missing is a configuration bug,
surfaced as a clear runtime error at hydrate time.
The class constructor to inspect
Array of all instruction field names (deduplicated)
StaticgetWalk the prototype chain unioning the static markupAugmenters
arrays declared at each level. Returned in parent-first →
child-last order so Mml.augment's fold applies the more
fundamental transformations first and the more specific
(deeper in the chain) ones last — same intuition as CSS
specificity.
Distinct from getAllSubscribableFields, which is leaf-first because a subclass's descriptor needs to override the parent's on the collision Map; augmenters have no override semantics, so we order for readability instead.
The augmenter signature (text, host, viewer) => string is
defined in api/mml.ts (alongside the Mml.augment static);
we use a structural local type here to avoid a runtime import
cycle (mixin.ts is below mml.ts in the layering).
StaticgetGet all persistent fields (from mixins and every class in the chain).
Walks the prototype chain and collects persistentFields declared at
every level — mixins carry them as static arrays, and concrete classes
do too. Since a subclass that declares its own persistentFields shadows
the parent's static in JS, we need hasOwnProperty at each level to
pick up contributions from the whole ancestry (Stuff → Idea → Location → …).
The class constructor to inspect
Array of all persistent field names (deduplicated)
StaticgetWalk the prototype chain unioning the static subscribableFields
arrays declared at each level. Mirrors the shape of
getAllPersistentFields. Order is parent-first → child-
last; later entries on the same name win in the caller's
downstream merge (collectSubscribableFields in mql-subscription.ts).
SubscribableFieldDescriptor is defined in mql-subscription.ts;
we use a structural local type here to avoid a runtime import
cycle (mixin.ts is below mql-subscription.ts in the layering).
StaticgetGet all persistent fields from mixins applied to a class. Walks the prototype chain collecting fields from all mixins.
This is used by PersistApi to automatically sync fields without requiring manual field lists in every class.
The class constructor to inspect
Array of persistent field names
StatichasCheck if a class or Stuff instance uses a specific mixin.
Two overloads:
hasMixin(constructor, name) — pure constructor check, walks
the prototype chain. Used for static introspection.hasMixin(stuffInstance, name) — also walks shadows attached
to the host. A host without the mixin but carrying a shadow
that composes it returns true. This is what the Witness
pattern relies on so that a shadow can opt into receiving
notifications even when the host doesn't compose the
relevant mixin itself.Usage:
import { Mixins } from './mixin';
if (MixinApi.hasMixin(Player, Mixins.Named)) {
// Player class composes NamedMixin
}
if (MixinApi.hasMixin(somePlayerInstance, Mixins.Named)) {
// The instance OR any shadow on it composes NamedMixin
}
Check if a class or Stuff instance uses a specific mixin.
Two overloads:
hasMixin(constructor, name) — pure constructor check, walks
the prototype chain. Used for static introspection.hasMixin(stuffInstance, name) — also walks shadows attached
to the host. A host without the mixin but carrying a shadow
that composes it returns true. This is what the Witness
pattern relies on so that a shadow can opt into receiving
notifications even when the host doesn't compose the
relevant mixin itself.Usage:
import { Mixins } from './mixin';
if (MixinApi.hasMixin(Player, Mixins.Named)) {
// Player class composes NamedMixin
}
if (MixinApi.hasMixin(somePlayerInstance, Mixins.Named)) {
// The instance OR any shadow on it composes NamedMixin
}
StaticisgetActiveMixins membership predicate. Returns true iff a mixin
named mixinName is in the active set on stuff. For un-gated
mixins, equivalent to hasMixin; for gated ones, reflects
augment-toggled state.
StaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisType-predicate narrowing helpers.
Each predicate performs a runtime mixin check and threads the matching interface into TypeScript's control-flow narrowing:
if (MixinApi.isContainer(obj)) {
obj.getContents(); // obj is Stuff & Container here
}
Prefer these over hasMixin(obj.constructor, Mixins.X) + cast when the
goal is to call interface methods on the narrowed object. hasMixin
remains the primitive for dynamic introspection (iterating queryMixins).
StaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisWhether obj can currently fulfill an order. MakerMixin is
augment-gated, so this routes through isActive (activeness),
not hasMixin (composition): a bar Crafter composes
MakerMixin always but is a maker only while its on-shift Position
confers it. The two isMaker consumers — CraftingLogic.resolveMaker
(order fulfilment) and BankingControllerBase (the house
representative) — thereby resolve only the on-shift bartender.
StaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticisStaticpascalConvert a lowerCamel field name to its PascalCase form — the
suffix used when deriving a method name from a field name (e.g.,
'coords' → 'Coords', so a hydrator can dispatch
'set' + pascalCase('coords') → 'setCoords').
Used by PersistentHydrator (Phase 1 set<X> / Phase 2
apply<X> dispatch) and Zone.lookupField (get<X> reflection).
Lives here because the field-name-to-method-name convention is
the same one getAllPersistentFields / getAllInstructionFields
presume — callers that introspect mixin field names also need
to derive method names from them.
Empty string passes through unchanged.
StaticqueryGet all mixins applied to a class by walking the prototype chain. Returns an array of constructor functions.
The class constructor to inspect
Array of mixin constructors
Static API for mixin management and introspection.