feat(engagement): sixteen in-universe bodies, 25 seeded rules, the governor's letter (Phase 11b) #23

Merged
whitlocktech merged 1 commits from feature/engagement-uo-templates into edge 2026-09-01 06:35:47 +00:00
Member

The module's half of Phase 11b — the content behind 11a's declarations. Needs website#178 (MODULE_API 1.9.0); decisions in docs#.

32 templates and 25 rules, shipped through core's new api.registerEngagementSeeds. Every rule enabled = 0 — the registry enforces it rather than trusting it.

The voice (decision 8)

The game-powered families read from inside Britannia, with a per-family in-fiction sender rather than one voice across all sixteen. That was the decision rather than the obvious answer: a shard where Lord Blackthorn writes to you personally about a champion spawn is a shard where the letter about your governorship means nothing.

Sender Families
Lord Blackthorn's court the governorship, the elections — the crown's business and nothing else
the Office of Deeds houses
the Merchants' Guild vendors
a guild herald guild departures and dissolutions
the town crier champion spawns
a guildmaster skills, quests
the Chronicler of the Dead deaths and murders
the keeper of the rolls leaderboards

Each ships an email body and an inapp body in the same voice — one rule fires on both at once, and a player who reads the inbox item and then the mail must not meet two different narrators. The digest stays core's generic notify.digest: a day of events rolled into a list is not a letter from anybody.

The governor's letter, rendered
SUBJECT: The seat of Britain passes to thee

By the hand of Lord Blackthorn

Darrow — the people of Britain have named thee their Governor in
succession to Mireille, and the Crown confirms it.

The seat carries duties as well as honours. A city is judged by what
its Governor troubles to build, and by what is allowed to fall into
disrepair while they hold the office. See that Britain is the better
for thy tenure.

The Crown will not govern in thy stead, nor will it stand between thee
and those who gave thee the seat. They may take it back.

What stays plain (decision 9)

Nine of the 25 point at core's notify.event / inapp.event and author nothing — §4.6.1 property 1 exercised at scale. The line is drawn where fiction costs something real: a failed-login notice written as "a stranger sought entry to thy account" is indistinguishable in register from the phishing mail it warns about, and a moderator reading uo.cheat.detected at two in the morning wants a name, a rule and a timestamp rather than a scroll.

Both account-security triggers, uo.server.up/down, and the five staff- and admin-ceilinged ones.

The governor's letter (decision 10) — uo.governor.appointed, the 25th trigger

§8.6 records that uo.points.rank_changed cannot address a person because top[] names a mobile serial — and the same reasoning was silently assumed to cover the governor. It does not. city.update's governor is written by BridgeJson.Actor(), which emits serial, name, acct and even webId. The winner is addressable today, with no protocol change.

It fires from the same frame, the same transition and the same never-on-first-sight guard as uo.governor.elected, which is unchanged. Two triggers rather than two audiences on one, because one trigger means one rule means one template — and the town's bulletin and the governor's letter are not the same text. An operator can run either alone.

Presentational fragments

A template has no conditionals by design, and an unset optional interpolates to the empty string — right for a structural body, wrong for a sentence: "Be it known that , recorded to thy name, is this day found ." So the ternary stays in the mapper and its result arrives as a declared optional (Phase 5a's forWhom precedent). Two shapes:

  • a LABEL always has a value and carries a sentence's spine — houseLabel falls back to a seal number, because a warning has to name something the owner can act on;
  • a TRAILING FRAGMENT may be empty and leads with its own space, so {{slainBy}}. closes as "has fallen." either way.

All additive optionals, so no trigger version bump.

What a render sweep found

Every body rendered twice — once with each declared example, once with required variables only — then scanned for the punctuation a vanished token leaves behind. Three real defects, none visible in the source and none catchable by any existing check:

  1. An optional variable in a SUBJECT LINE. A notice concerning thy house at {{region}}"…thy house at ". A subject is the one place a hole is unmissable and the one place no block validator looks.
  2. Multi-optional detail lines"On hand: gold. Charged each period: gold." on a pre-v5 vendor frame. Now assembled in the mapper from the parts actually present, the same argument place() already makes for coordinates; a frame with nothing to report produces no line rather than a punctuated empty one.
  3. A trailing fragment used as a leading one, opening an in-app body with a stray space.

The labels stay required: false deliberately — a required variable missing refuses the emit, and a dropped notification is worse than a cosmetic hole. That leaves nothing at runtime to notice a mapper that forgot one, so engagementSeeds.test.js asserts every label is supplied on every path that emits its trigger.

One more trap worth naming

uo.champ.boss_up is a legal trigger id (§7.2 relaxed the grammar to admit _ so uo.house.idoc_warning could exist) and an illegal template key — core's key pattern admits . and -, not _. Its body is keyed uo.champ.boss-up. Caught at registration as a boot failure, and asserted here so the next person meets it as a test name.

Verification

  • 524 module tests green, 0 failing (16 new).
  • check:imports and check:bundle clean. check:swagger reports STALE from CRLF alone and regenerates byte-identical — no route changed.
  • All 32 bodies validated against the real block registry and the real trigger declarations: every variable declared, every body renders.
  • module.jsoncoreApi: "^1.9.0", version 0.5.0.

AI-assisted: written with Claude Code.

🤖 Generated with Claude Code

The module's half of Phase 11b — the content behind 11a's declarations. Needs **website#178** (`MODULE_API` 1.9.0); decisions in **docs#**. 32 templates and 25 rules, shipped through core's new `api.registerEngagementSeeds`. **Every rule `enabled = 0`** — the registry enforces it rather than trusting it. ## The voice (decision 8) The game-powered families read from inside Britannia, with a **per-family in-fiction sender** rather than one voice across all sixteen. That was the decision rather than the obvious answer: a shard where Lord Blackthorn writes to you personally about a champion spawn is a shard where the letter about your governorship means nothing. | Sender | Families | | --- | --- | | **Lord Blackthorn's court** | the governorship, the elections — the crown's business and nothing else | | **the Office of Deeds** | houses | | **the Merchants' Guild** | vendors | | **a guild herald** | guild departures and dissolutions | | **the town crier** | champion spawns | | **a guildmaster** | skills, quests | | **the Chronicler of the Dead** | deaths and murders | | **the keeper of the rolls** | leaderboards | Each ships an `email` body **and** an `inapp` body in the same voice — one rule fires on both at once, and a player who reads the inbox item and then the mail must not meet two different narrators. The **digest** stays core's generic `notify.digest`: a day of events rolled into a list is not a letter from anybody. <details><summary>The governor's letter, rendered</summary> ``` SUBJECT: The seat of Britain passes to thee By the hand of Lord Blackthorn Darrow — the people of Britain have named thee their Governor in succession to Mireille, and the Crown confirms it. The seat carries duties as well as honours. A city is judged by what its Governor troubles to build, and by what is allowed to fall into disrepair while they hold the office. See that Britain is the better for thy tenure. The Crown will not govern in thy stead, nor will it stand between thee and those who gave thee the seat. They may take it back. ``` </details> ## What stays plain (decision 9) Nine of the 25 point at core's `notify.event` / `inapp.event` and author nothing — §4.6.1 property 1 exercised at scale. The line is drawn where fiction costs something real: a failed-login notice written as *"a stranger sought entry to thy account"* is indistinguishable in register from the phishing mail it warns about, and a moderator reading `uo.cheat.detected` at two in the morning wants a name, a rule and a timestamp rather than a scroll. Both account-security triggers, `uo.server.up`/`down`, and the five staff- and admin-ceilinged ones. ## The governor's letter (decision 10) — `uo.governor.appointed`, the 25th trigger §8.6 records that `uo.points.rank_changed` cannot address a person because `top[]` names a mobile serial — and **the same reasoning was silently assumed to cover the governor. It does not.** `city.update`'s `governor` is written by `BridgeJson.Actor()`, which emits `serial`, `name`, **`acct`** and even `webId`. The winner is addressable **today**, with no protocol change. It fires from the same frame, the same transition and the same never-on-first-sight guard as `uo.governor.elected`, which is unchanged. Two triggers rather than two audiences on one, because **one trigger means one rule means one template** — and the town's bulletin and the governor's letter are not the same text. An operator can run either alone. ## Presentational fragments A template has no conditionals by design, and an unset optional interpolates to the **empty string** — right for a structural body, wrong for a sentence: *"Be it known that , recorded to thy name, is this day found ."* So the ternary stays in the mapper and its result arrives as a declared optional (Phase 5a's `forWhom` precedent). Two shapes: - a **LABEL** always has a value and carries a sentence's spine — `houseLabel` falls back to a seal number, because a warning has to name *something* the owner can act on; - a **TRAILING FRAGMENT** may be empty and leads with **its own space**, so `{{slainBy}}.` closes as *"has fallen."* either way. All additive optionals, so no trigger version bump. ## What a render sweep found Every body rendered twice — once with each declared `example`, once with **required variables only** — then scanned for the punctuation a vanished token leaves behind. **Three real defects, none visible in the source and none catchable by any existing check:** 1. **An optional variable in a SUBJECT LINE.** `A notice concerning thy house at {{region}}` → *"…thy house at "*. A subject is the one place a hole is unmissable and the one place no block validator looks. 2. **Multi-optional detail lines** — *"On hand: gold. Charged each period: gold."* on a pre-v5 vendor frame. Now assembled in the mapper from the parts actually present, the same argument `place()` already makes for coordinates; a frame with nothing to report produces no line rather than a punctuated empty one. 3. **A trailing fragment used as a leading one**, opening an in-app body with a stray space. The labels stay `required: false` **deliberately** — a required variable missing *refuses the emit*, and a dropped notification is worse than a cosmetic hole. That leaves nothing at runtime to notice a mapper that forgot one, so `engagementSeeds.test.js` asserts every label is supplied on every path that emits its trigger. ## One more trap worth naming `uo.champ.boss_up` is a legal **trigger id** (§7.2 relaxed the grammar to admit `_` so `uo.house.idoc_warning` could exist) and an **illegal template key** — core's key pattern admits `.` and `-`, not `_`. Its body is keyed `uo.champ.boss-up`. Caught at registration as a boot failure, and asserted here so the next person meets it as a test name. ## Verification - **524 module tests green, 0 failing** (16 new). - `check:imports` and `check:bundle` clean. `check:swagger` reports STALE from CRLF alone and regenerates byte-identical — no route changed. - All 32 bodies validated against the real block registry and the real trigger declarations: every variable declared, every body renders. - `module.json` → `coreApi: "^1.9.0"`, version 0.5.0. --- AI-assisted: written with Claude Code. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
wtclaude added 1 commit 2026-09-01 06:16:07 +00:00
feat(engagement): sixteen in-universe bodies, 25 seeded rules, the governor's letter (Phase 11b)
Some checks failed
PR Checks / client-build (pull_request) Successful in 17s
PR Checks / server-tests (pull_request) Successful in 22s
PR Checks / frozen-manifest (pull_request) Failing after -34s
50a89b48e2
11a declared the triggers; this is the content behind them. Ships through
core's new api.registerEngagementSeeds (MODULE_API 1.9.0): 32 templates and 25
rules, every rule enabled = 0.

THE VOICE (decision 8). The game-powered families read from inside Britannia,
with a per-family in-fiction sender rather than one voice across all sixteen —
Lord Blackthorn's court writes about the crown's business (the seat, the ballot)
and nothing else, because a shard where Blackthorn writes to you personally about
a champion spawn is a shard where the letter about your governorship means
nothing. The Office of Deeds has houses, the Merchants' Guild vendors, a herald
guilds, the town crier champion spawns, a guildmaster skills and quests, the
Chronicler deaths, the keeper of the rolls leaderboards.

WHAT STAYS PLAIN (decision 9). Nine of the 25 point at core's notify.event /
inapp.event and author nothing, and the line is drawn where fiction costs
something real: a failed-login notice written as "a stranger sought entry to thy
account" is indistinguishable in register from the phishing mail it warns about,
and a moderator reading uo.cheat.detected at 2am wants a name, a rule and a
timestamp rather than a scroll. Both account-security triggers, server up/down,
and the five staff/admin-ceiling ones.

THE GOVERNOR'S LETTER (decision 10) — uo.governor.appointed, the 25th trigger.
§8.6 records that uo.points.rank_changed cannot address a person because top[]
names a mobile serial, and the same reasoning was silently assumed to cover the
governor. It does not: city.update's `governor` is written by BridgeJson.Actor(),
which emits serial, name, acct AND webId. The winner is addressable today with no
protocol change. It fires from the same frame, the same transition and the same
never-on-first-sight guard as uo.governor.elected, which stays exactly as
declared — the town's bulletin and the governor's letter are two triggers because
one trigger means one rule means one template, and they are not the same text.
An operator can run either alone.

PRESENTATIONAL FRAGMENTS, because a template has no conditionals by design and an
unset optional interpolates to the empty string. Phase 5a's `forWhom` precedent:
the ternary stays in the mapper and its result arrives as a declared optional.
Two shapes — a LABEL always has a value and carries a sentence's spine
(houseLabel falls back to a seal number); a TRAILING FRAGMENT may be empty and
leads with its own space, so `{{slainBy}}.` closes as "has fallen." either way.
Additive, so no version bump.

A render sweep over all 32 bodies, twice — once with every declared example and
once with required variables only — is what found these. Three defects it caught:
an optional `{{region}}` in a subject line ("A notice concerning thy house at ");
multi-optional ledger lines rendering "On hand:  gold. Charged each period:
gold." on a pre-v5 frame, now assembled in the mapper from the parts actually
present, the same argument place() already makes; and a leading trailing-fragment
opening a body with a stray space.

The labels stay `required: false` deliberately — a missing one must never REFUSE
an emit, since a dropped notification is worse than a cosmetic hole — so nothing
at runtime would notice a mapper that forgot one. engagementSeeds.test.js is what
notices.

524 module tests green; check:imports and check:bundle clean. check:swagger
reports STALE from CRLF alone and regenerates byte-identical — no route changed.

Refs docs ENGAGEMENT.md Phase 11b, decisions 8, 9, 10.

Co-Authored-By: Claude <noreply@anthropic.com>
whitlocktech merged commit 52d9c3ddb8 into edge 2026-09-01 06:35:47 +00:00
whitlocktech deleted branch feature/engagement-uo-templates 2026-09-01 06:35:48 +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#23
No description provided.