From fba337e5303c98956b8b6f6ad2d812c587cd93d7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 08:02:05 -0500 Subject: [PATCH] =?UTF-8?q?feat(protocol2):=20presence=20stream=20?= =?UTF-8?q?=E2=80=94=20online=20population=20+=20region=20transitions=20(P?= =?UTF-8?q?art=20B=20ph.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overlay BridgePresence (new): - presence.online sweep over online PlayerMobiles: total plus per-facet and per-region counts, emitted only when the population changes. - region.enter real-time from EventSink.OnEnterRegion (player-filtered), the cheap location signal PLAN.md prefers over Movement. - PresenceSweepSeconds (30s); wired into [bridge reload|sweepnow|status. Sidecar: - GET /online serves the latest presence.online snapshot from the event store (survives restart); population time series via /history?kind=presence.online. Docs: INTEGRATION.md presence events + /online endpoint; PROTOCOL_2 ph.2 built. Verified: sidecar cargo check clean; overlay compiles in the full ServUO Scripts tree (0 errors, 0 warnings). Live run pending. Co-Authored-By: Claude Opus 4.8 --- link/INTEGRATION.md | 26 ++++++++++++++++++++++++++ link/PROTOCOL_2.md | 4 ++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/link/INTEGRATION.md b/link/INTEGRATION.md index 3960840..5d5f012 100644 --- a/link/INTEGRATION.md +++ b/link/INTEGRATION.md @@ -280,6 +280,22 @@ In modern ServUO the "mayor" of a town is the **City Loyalty Governor**. The set Render the current board from `GET /governors` (§6) on connect, then keep it live with these events. +#### Presence (Protocol 2.0) + +Who's online and where. A population snapshot is polled (`PresenceSweepSeconds`, default 30s) and emitted **only when it changes**; region transitions arrive in real time. + +| kind | fields | notes | +|------|--------|-------| +| `presence.online` | `count`, `byFacet` `{map: n}`, `byRegion` `{region: n}` | The current online population. Emitted when the count or any breakdown changes. `GET /online` gives the latest; `GET /history?kind=presence.online` the time series. | +| `region.enter` | `from` (or null), `to` (or null), `map`, `who` (actor object) | A player crossed into a new named region. `from`/`to` are region names (`Wilderness` is unnamed). Cheap "who's where" feed. | + +```json +{"kind":"presence.online","count":42,"byFacet":{"Felucca":12,"Trammel":30}, + "byRegion":{"Britain":18,"Wilderness":9,"Despise":2},"t":1752489280000} +{"kind":"region.enter","from":"Britain","to":"Despise","map":"Felucca", + "who":{"serial":"0x1A2B","name":"Darrow","acct":"whitlocktech","player":true},"t":...} +``` + --- ## 5. REST — read queries @@ -576,6 +592,16 @@ GET /governors Every city's latest governance snapshot — the live board, kept current by the `city.update` stream (§4). Empty if the shard does not run the City Loyalty system. Ordered by city. +### Online population (Protocol 2.0) + +``` +GET /online +→ {"kind":"presence.online","count":42,"byFacet":{"Felucca":12,"Trammel":30}, + "byRegion":{"Britain":18,"Wilderness":9},"t":1752489280000} +``` + +The current online population — total plus per-facet and per-region breakdowns. The latest `presence.online` snapshot (from SQLite, so it survives a sidecar restart); keep it live with the `presence.online` stream (§4). `count: 0` with empty maps if the shard hasn't reported yet. For the population time series, `GET /history?kind=presence.online`. + --- ## 7. Status codes diff --git a/link/PROTOCOL_2.md b/link/PROTOCOL_2.md index 5455b73..9a5b993 100644 --- a/link/PROTOCOL_2.md +++ b/link/PROTOCOL_2.md @@ -381,8 +381,8 @@ If the website mirrors rosters/links (it does — `store.record_link`), it must ## 13. Part B phasing 1. ~~**Guilds + governors.**~~ **Built (2026-07-17), compiles clean both sides.** `BridgeSocial.cs` (guild sweep + `JoinGuild` → `guild.update`/`guild.remove`/`guild.join`) and `BridgeGovernance.cs` (city sweep → `city.update`, gated on `CityLoyaltySystem.Enabled`), `GuildSweepSeconds` (60s) / `CitySweepSeconds` (300s) config, both wired into `[bridge reload|sweepnow|status`. Sidecar `guilds`/`governors` board tables + `GET /guilds`, `/governors` served from the store (the §12.2 snapshot rule). Shared `BridgeJson.Actor` writer (serial/name/acct/webId/player). **Deviation from the §10 sketch:** the wire uses full-state `guild.update`/`city.update` upserts (website derives "created"/"governor changed" from the board) rather than discrete `guild.created`/`city.governor` events — this avoids a reconnect re-emit looking like a storm of creations, matching the proven `champ.update` model. *Live end-to-end run still pending.* -2. **Presence.** Who's-online/population sweep + region presence (`OnEnterRegion`) → `GET /online`, population history in the store. -3. **Housing registry.** Extend the decay sweep to a full owner→houses list + houses-for-sale → `GET /houses`. (Selected from the §11 menu.) +2. ~~**Presence.**~~ **Built (2026-07-17), compiles clean both sides.** `BridgePresence.cs`: a `presence.online` sweep (total + per-facet + per-region, emitted on change) and real-time `region.enter` (`EventSink.OnEnterRegion`, player-filtered). `PresenceSweepSeconds` (30s), wired into `[bridge`. `GET /online` serves the latest snapshot from the event store (population series via `/history?kind=presence.online`). *Live run pending.* +3. **Housing registry.** Extend the decay sweep to a full owner→houses list → `GET /houses`. (Selected from the §11 menu. Note: stock ServUO has no "for sale" flag on houses, so the registry is owner→houses; for-sale is dropped.) 4. **Titles.** `char.profile` `titles` block (§10.3) — no new stream, folds into `BridgeProfile`. 5. **Factions/VvV** — only after confirming which system the shard runs; stream just the enabled one.