`uo.creature.spawn`, `uo.boss.spawn`, `uo.npc.place`, `uo.gate.open` and
`uo.decor.place`, over protocol 7's one command family. Five actions because
five is what an author has; one `perform`/`revert`/`reconcile` because on the
wire they are one thing.
Five new budget dimensions -- `uo.creatures`, `uo.bosses`, `uo.npcs`,
`uo.decor`, `uo.gate.minutes` -- all declared by THIS MODULE (org lead,
2026-09-07). Core meters whatever dimensions a module declares and holds no UO
knowledge, which is the whole of what MODULE_API means by game-agnostic. A gate
is priced in minutes rather than in gates: one standing all day and twelve
standing five minutes each are not the same imposition on a world.
`reconcile()` ASKS the shard, and is the one place in this file that must not
use `reconcileByBootId`. A crier line lives in shard memory, so a changed
`bootId` IS proof it is gone; a spawned creature is in the world SAVE and
survives the restart the stamp would report it lost by. Anything `world.owned`
does not list is gone -- safe only because the shard's registry and the objects
it describes are written by the same save.
Teardown reports `gone` as success and `refused` as failed. A creature a player
killed is the point of having spawned it, and a run that ended `incomplete`
because its event worked would be a report nobody could read. `refused` means
the shard denies this run ever owned the serial, so nothing will delete it
through this path and the row must land unresolved with a reason.
The atlas gains a decoration index, parsed from the shard's own
`Data/Decoration/**/*.cfg` -- 120 files, read RECURSIVELY because the real tree
nests two deep and a flat read would index a fraction of it while looking like
it worked. 313 distinct types. The decor verb resolves through it rather than
passing a type name through, which keeps the verb to this shard's own decoration
vocabulary AND fetches the item id: `Static` alone accounts for 5031 placements
under 1992 different graphics, so a bare type name places the wrong thing.
`PARSER_VERSION` -> 3, so an already-imported tree is re-read.
Two things the build found in code that had already shipped:
`uo.options.creatures` answered with the atlas SLUG -- unique, stable, and not
something the shard can build, because a creature is constructed from a ServUO
class name and `orc-brute` is not one. The atlas's `name` is the raw type token
from the spawn files, so the fix was to stop discarding the half that works.
Safe to change because Phase 12a is the source's first consumer; the file said
so when it shipped.
`uo.npc.place` could not be performed from its own required params. Both ends
refuse an oracle with neither a greeting nor a line, but both fields were
optional -- so a cross-field rule sat where no authoring form could render it.
The greeting is now `required`, which says the same thing in the contract
itself. Caught by the existing dry-run sweep, which is a better argument for
that test than anything written about it when it shipped.
605 tests pass. `swagger-fragment.json` is stale on `edge` already and this
phase adds no route, so it is left alone.
Refs: docs/link/v7.md, docs/website/EVENTS_PLAN.md Phase 12a
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
The data half of the extraction: 8 model directories, 13 utils, the shard
stream catalog and the 27-table schema fragment with its purge.
server/core.js is what makes the port a one-line import change per file rather
than a signature change per function. Ported code requires its dependencies at
file scope -- `const { query } = require('../../core')` -- which runs before
register() has been called and before any ctx exists. So every member is a
stable function that resolves ctx when CALLED, and nothing may be destructured
off ctx at init either, because core is free to hand over a getter.
Two helpers are vendored rather than taken from ctx, and the line between them
is the point. utils/excerpt.js is core's deriveExcerpt -- nine lines of pure
text handling. Core's sanitiser next to it was NOT copied: a second copy of a
security control diverges silently the moment either is fixed. announceLinks.js
vendors legError and articleUrl the same way, but baseUrl could not be: core's
reads APP_BASE_URL, and §2.7 forbids a module reading core's environment, so it
comes off ctx.site.baseUrl.
The schema fragment is core's 27 shard_*/uo_link_* statements, verbs CREATE,
ALTER and UPDATE only, every CREATE TABLE guarded. Two of its tables carry a
foreign key INTO users, which is allowed and is why the replay order matters --
core's schema is in place before this runs. The reverse never occurs and must
not: it would make core unable to boot without a module installed.
One real port bug caught by the integration run, not by tests: the atlas art
map resolved `../../../db/data`, which pointed at core's tree when this file
lived there and points outside server/ now. A path that happens to resolve is
exactly what survives a green suite, because the absent-file branch returns {}
and looks like the normal case.
Co-Authored-By: Claude <noreply@anthropic.com>