feat(events): the five world verbs an author sees (Phase 12a) #31

Merged
whitlocktech merged 1 commits from feature/events-p12a-world-verbs into edge 2026-09-07 06:58:06 +00:00
Member

Events Phase 12a, module half. One of four: servuo-plugins (servuo-plugins#23), link (link#38), Module-uo, docs (docs#221) — all onto edge. Spec of record: docs/link/v7.md.

What an author gains

uo.creature.spawn, uo.boss.spawn, uo.npc.place, uo.gate.open, uo.decor.place. Five actions because five is what an author has; one shared 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.minutesall 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, and a count would price them identically.

MODULE_API_VERSION does not move. Budget dimensions and option sources are things a module declares through an API that already exists.

reconcile() asks, and this is the one place in the file that must

A crier line lives in shard memory, so a changed bootId is proof it is gone — that is what reconcileByBootId exists for. A spawned creature is in the world save and survives the restart the stamp would report it lost by. So the world verbs ask world.owned, and anything not listed 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: nothing will delete it through this path, so the row must land unresolved with a reason rather than be quietly marked reverted.

The decoration index

The atlas gains one, parsed from the shard's own Data/Decoration/**/*.cfg. 120 files, read recursively — the real tree nests two deep (Magincia/Trammel, Stygian Abyss/Ter Mur, Old/Britannia) and a flat read would index a fraction of it while looking like it worked, the failure being a dropdown quietly missing whole expansions. 313 distinct types on this tree.

The decor verb resolves through it rather than passing a type name straight to the shard, which does two things at once: it keeps the verb to this shard's own decoration vocabulary (tighter than "any item that is not a container"), and it fetches the item idStatic alone accounts for 5031 placements under 1992 different graphics, so for that class the graphic is the identity and a bare type name places the wrong thing. 131 of the 313 types carry more than one id.

PARSER_VERSION → 3, so an already-imported tree is re-read rather than skipped on an unchanged hash.

Two defects the build found in code that had already shipped

uo.options.creatures answered with something the shard cannot use. It shipped in Phase 9, before anything consumed it, carrying the atlas slug — unique, stable, and not a thing 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 (displayName picks the best-attested spelling), 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 — it would stand there in silence — but both fields were declared optional, so a cross-field "at least one of these" rule sat where no authoring form could render it as valid. The greeting is now required, which says the same thing in the contract core reads as data; the perform() check remains, for the field holding nothing but spaces.

It was caught by the existing dry-run sweep — the test that performs every action with only its required params — which is a better argument for that test than anything written about it when it shipped.

Checks

npm test: 605 pass, 0 fail (was 598). check:imports and check:bundle clean.

check:swagger reports the fragment stale — it is already stale on edge (verified by stashing this branch's changes and re-running), and this phase adds no route, so it is left alone rather than regenerated inside an unrelated change.

🤖 Generated with Claude Code

https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4

Events Phase 12a, module half. One of four: `servuo-plugins` (servuo-plugins#23), `link` (link#38), `Module-uo`, `docs` (docs#221) — all onto `edge`. Spec of record: [`docs/link/v7.md`](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/221). ## What an author gains `uo.creature.spawn`, `uo.boss.spawn`, `uo.npc.place`, `uo.gate.open`, `uo.decor.place`. **Five actions because five is what an author has**; one shared `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, and a count would price them identically. `MODULE_API_VERSION` does **not** move. Budget dimensions and option sources are things a module *declares* through an API that already exists. ## `reconcile()` asks, and this is the one place in the file that must A crier line lives in shard memory, so a changed `bootId` **is** proof it is gone — that is what `reconcileByBootId` exists for. A spawned creature is in the world **save** and survives the restart the stamp would report it lost by. So the world verbs ask `world.owned`, and anything not listed 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: nothing will delete it through this path, so the row must land unresolved with a reason rather than be quietly marked reverted. ## The decoration index The atlas gains one, parsed from the shard's own `Data/Decoration/**/*.cfg`. **120 files, read recursively** — the real tree nests two deep (`Magincia/Trammel`, `Stygian Abyss/Ter Mur`, `Old/Britannia`) and a flat read would index a fraction of it while looking like it worked, the failure being a dropdown quietly missing whole expansions. **313 distinct types** on this tree. The decor verb resolves *through* it rather than passing a type name straight to the shard, which does two things at once: it keeps the verb to this shard's own decoration vocabulary (tighter than "any item that is not a container"), and it fetches the **item id** — `Static` alone accounts for **5031 placements under 1992 different graphics**, so for that class the graphic is the identity and a bare type name places the wrong thing. 131 of the 313 types carry more than one id. `PARSER_VERSION` → 3, so an already-imported tree is re-read rather than skipped on an unchanged hash. ## Two defects the build found in code that had already shipped **`uo.options.creatures` answered with something the shard cannot use.** It shipped in Phase 9, before anything consumed it, carrying the atlas **slug** — unique, stable, and not a thing 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 (`displayName` picks the best-attested spelling), 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 — it would stand there in silence — but both fields were declared optional, so a cross-field *"at least one of these"* rule sat where no authoring form could render it as valid. The greeting is now `required`, which says the same thing in the contract core reads as data; the `perform()` check remains, for the field holding nothing but spaces. It was caught by the **existing dry-run sweep** — the test that performs every action with only its required params — which is a better argument for that test than anything written about it when it shipped. ## Checks `npm test`: **605 pass, 0 fail** (was 598). `check:imports` and `check:bundle` clean. `check:swagger` reports the fragment stale — **it is already stale on `edge`** (verified by stashing this branch's changes and re-running), and this phase adds no route, so it is left alone rather than regenerated inside an unrelated change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
wtclaude added 1 commit 2026-09-07 06:54:49 +00:00
feat(events): the five world verbs an author sees (Phase 12a)
All checks were successful
PR Checks / client-build (pull_request) Successful in 28s
PR Checks / server-tests (pull_request) Successful in 30s
PR Checks / frozen-manifest (pull_request) Successful in 43s
89be9d6a4e
`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
whitlocktech merged commit 0b142adb81 into edge 2026-09-07 06:58:06 +00:00
whitlocktech deleted branch feature/events-p12a-world-verbs 2026-09-07 06:58:07 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: RunicGateway/Module-uo#31
No description provided.