docs(link): record world.ruleset and mark Protocol 3.0 progress
Protocol 3.0 order 2 (v3.md §5) is built across all four repos; this is its
documentation half, plus the running progress record the plan was missing.
v3.md
- A progress table at the top and a State column on §9's sequencing table, so
"what has landed" is answerable without reading four git logs. Part A (order
1) and world.ruleset (order 2) are marked done; the spawn atlas is next.
- §5 gains the implementation notes worth keeping, chiefly: where a system's
on/off state is DERIVED rather than configured, read the system's own static
instead of inventing a .cfg key (Shadowguard has no Enabled key — it's the
TOL expansion gate; Factions is `!ViceVsVirtueSystem.Enabled` by
construction in stock ServUO). Also that the plugin CAN be compile-verified
despite the "no standalone build" caveat, and how.
INTEGRATION.md
- The world.ruleset catalog entry and GET /ruleset, with the two things
consumers get wrong: caps are in TENTHS (1000 = 100.0), and `connect` exists
only if the operator set Bridge.PublicConnectAddress — the shard's real
listen address is never published.
- §2 now says plainly that v3 has NOT been bumped yet and what that means:
sidecars on `edge` report 2 while already carrying some v3 kinds, so do not
infer feature availability from the version during this window.
PROTOCOL_2.md §10.4
- The deferred "which PvP system does this shard run?" is answered (VvV on,
Factions off — and mutually exclusive by construction), and world.systems is
marked superseded by world.ruleset, which carries the systems block it asked
for. No orphan kind is left behind.
BACKEND_DESIGN.md — the shard_ruleset table (why it is stored whole rather than
normalized, and why no row means null rather than {}) and the public route.
PROJECT_TREE.md is deliberately untouched: sync-project-tree regenerates it on
push to main, so it updates itself at the v3 cutover.
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,20 @@ Pin the version you built against and compare it to the header (or `/health.prot
|
|||||||
|
|
||||||
**v2 (Protocol 2.0)** added the account-provisioning surface (§6.x: `POST /accounts/create`, `DELETE /link/{account}`) and the `account.*` events. Outbound event kinds are **additive** — a v1 client that ignores unknown kinds keeps working against the live feed — but the new *endpoints* require a v2 sidecar. If you send `X-UOLink-Version: 1`, calls to the new endpoints are refused with the 409 above.
|
**v2 (Protocol 2.0)** added the account-provisioning surface (§6.x: `POST /accounts/create`, `DELETE /link/{account}`) and the `account.*` events. Outbound event kinds are **additive** — a v1 client that ignores unknown kinds keeps working against the live feed — but the new *endpoints* require a v2 sidecar. If you send `X-UOLink-Version: 1`, calls to the new endpoints are refused with the 409 above.
|
||||||
|
|
||||||
|
**v3 (Protocol 3.0) is being built and the version has not been bumped yet.** It is defined as *adds
|
||||||
|
`world.ruleset`, `points.board`, `vendor.listing` / `vendor.listing.remove`*, and the bump to
|
||||||
|
`X-UOLink-Version: 3` happens **exactly once**, at the end, when [`v3.md`](v3.md) §4's `edge` → `main`
|
||||||
|
cutover lands — because a bump is an operator-visible hard break (409 on every protected route, and
|
||||||
|
the website's WS closes on the `ws.hello` mismatch), so doing it per phase would break the site
|
||||||
|
repeatedly.
|
||||||
|
|
||||||
|
Until then, sidecars on `edge` still report `2` while already carrying some v3 kinds and endpoints.
|
||||||
|
That is safe in the direction that matters: event kinds are additive, and a client that ignores
|
||||||
|
unknown kinds and tolerates a `404` on a not-yet-present endpoint keeps working. What you must **not**
|
||||||
|
do is infer feature availability from the version number during this window — probe the endpoint, or
|
||||||
|
treat a missing `world.ruleset` as "this shard hasn't published one". There is deliberately **no
|
||||||
|
feature-negotiation array**: v3 implies all three kinds.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Health
|
## 3. Health
|
||||||
@@ -315,6 +329,58 @@ The house registry — one row per house, complementing the `house.decay` *trans
|
|||||||
|
|
||||||
Render from `GET /houses` (§6) on connect, then keep live with these events.
|
Render from `GET /houses` (§6) on connect, then keep live with these events.
|
||||||
|
|
||||||
|
#### Shard ruleset (Protocol 3.0)
|
||||||
|
|
||||||
|
How the shard is actually configured, published by the shard itself. **Not a sweep** — it changes only
|
||||||
|
when an operator edits `Config/*.cfg`, so it is emitted once per shard↔sidecar connect (and on
|
||||||
|
`[bridge reload`), exactly like `server.hello`.
|
||||||
|
|
||||||
|
| kind | fields | notes |
|
||||||
|
|------|--------|-------|
|
||||||
|
| `world.ruleset` | `rev`, `shard`, `expansion`, `connect?`, `systems`, `caps`, `housing`, `accounts`, `vetRewards`, `loot`, `vendors`, `champions?`, `treasureMaps`, `vvv?`, `store`, `schedule?` | The whole ruleset, always complete — **never a delta**, so the latest frame replaces the previous one outright. Every block except `shard`/`expansion` is optional and is **omitted when its system is off**, so absence means "not applicable here", not "unknown". |
|
||||||
|
|
||||||
|
`rev` is the shard's FNV-1a of the body: identical `rev` means the ruleset is unchanged and this frame
|
||||||
|
is just a reconnect re-send, so a consumer can skip the write. It is deliberately **not**
|
||||||
|
`String.GetHashCode()`, which is seeded per process and would change on every shard restart.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"kind":"world.ruleset","rev":"1a2b3c4d","shard":"UOMysticmoon","expansion":"EJ",
|
||||||
|
"systems":{"cityLoyalty":true,"vvv":true,"factions":false,"siege":false,"chat":true,
|
||||||
|
"store":true,"dailyRares":true,"honesty":true,"shadowguard":true,
|
||||||
|
"treasureMaps":true,"vetRewards":true,"testCenter":false},
|
||||||
|
"caps":{"skill":1000,"totalSkill":7000,"stat":225,"str":125,"dex":125,"int":125,
|
||||||
|
"strMax":150,"dexMax":150,"intMax":150},
|
||||||
|
"housing":{"accountHouseLimit":1},
|
||||||
|
"accounts":{"perIp":3,"charSlots":7,"autoCreate":true},
|
||||||
|
"vetRewards":{"enabled":true,"rewardIntervalDays":30},
|
||||||
|
"loot":{"feluccaLuckBonus":1000,"feluccaBudgetBonus":100,"feluccaMaxProps":11},
|
||||||
|
"vendors":{"restockDelayMinutes":60,"maxSell":500,"economyStockAmount":500},
|
||||||
|
"champions":{"powerScrolls":6,"statScrolls":16,"scrollChance":0.1,
|
||||||
|
"transcendenceChance":50.0,"rankThresholds":[5,10,13]},
|
||||||
|
"treasureMaps":{"enabled":true,"lootChance":0.01,"resetDays":30},
|
||||||
|
"vvv":{"enabled":true,"startSilver":2000,"enhancedRules":false},
|
||||||
|
"store":{"enabled":true,"currencyName":"Sovereigns"},
|
||||||
|
"schedule":{"autoSaveEnabled":true,"autoSaveFrequencyMinutes":15,"autoRestartEnabled":false},
|
||||||
|
"t":1752489280000}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Two things consumers get wrong.**
|
||||||
|
|
||||||
|
1. **`caps.skill` and `caps.totalSkill` are in tenths**, the way ServUO stores them: `1000` is `100.0`
|
||||||
|
skill and `7000` is `700.0` total. Rendering the raw number is actively misleading. The other caps
|
||||||
|
(`stat`, `str`, …) are plain integers.
|
||||||
|
2. **`connect` is present only if the operator set `Bridge.PublicConnectAddress`.** The shard's real
|
||||||
|
listen address (`Server.cfg`) is never published; nor are `Staff.cfg`, `Email.cfg`, `DataPath.cfg`,
|
||||||
|
`Bridge.cfg`, `Compiler.cfg`, `Reports.cfg` or `Client.cfg`. The frame is built from an explicit
|
||||||
|
allowlist in `BridgeRuleset.cs` — `Config.Entries` is never enumerated, because that would sweep in
|
||||||
|
every key on the server.
|
||||||
|
|
||||||
|
Absent entirely if the shard runs `Bridge.RulesetEnabled=false` or an older plugin. Render from
|
||||||
|
`GET /ruleset` (§6) on connect, then keep live with this event.
|
||||||
|
|
||||||
|
This **supersedes the `world.systems` frame** sketched in [`PROTOCOL_2.md`](PROTOCOL_2.md) §10.4 and
|
||||||
|
never implemented; the `systems` block above is what that asked for.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. REST — read queries
|
## 5. REST — read queries
|
||||||
@@ -658,6 +724,22 @@ GET /houses
|
|||||||
|
|
||||||
Every house's latest snapshot — owner→houses map. Served from the sidecar's projection, kept current by the `house.*` stream (§4). Ordered by name. Survives a sidecar restart.
|
Every house's latest snapshot — owner→houses map. Served from the sidecar's projection, kept current by the `house.*` stream (§4). Ordered by name. Survives a sidecar restart.
|
||||||
|
|
||||||
|
### Shard ruleset (Protocol 3.0)
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /ruleset
|
||||||
|
→ { "ruleset": {"kind":"world.ruleset","rev":"1a2b3c4d","shard":"UOMysticmoon",
|
||||||
|
"expansion":"EJ","systems":{...},"caps":{...},"accounts":{...}, ... } }
|
||||||
|
```
|
||||||
|
|
||||||
|
The shard's published ruleset (§4 for the full frame and its two gotchas). Served from the sidecar's
|
||||||
|
store, so it **answers while the shard is down** — a rules page that goes blank during a restart is
|
||||||
|
worse than one that is briefly stale. Keep it current with the `world.ruleset` stream.
|
||||||
|
|
||||||
|
`{"ruleset": null}` means the shard has never published one — an older plugin, or
|
||||||
|
`Bridge.RulesetEnabled=false`. That is a real answer distinct from a published ruleset, and worth
|
||||||
|
rendering differently ("not published yet") rather than as an empty ruleset.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Status codes
|
## 7. Status codes
|
||||||
|
|||||||
13
link/PLAN.md
13
link/PLAN.md
@@ -315,6 +315,14 @@ Counts in `hello` are a live snapshot taken on the Core thread, not a cached val
|
|||||||
7. **Core edit: `PlayerVendorSale`** (§6). Then the cheat-detection feed.
|
7. **Core edit: `PlayerVendorSale`** (§6). Then the cheat-detection feed.
|
||||||
8. **Cheat signals.** `FastWalk`, `OnPropertyChanged` audit, vendor-sale anomaly detection in the sidecar.
|
8. **Cheat signals.** `FastWalk`, `OnPropertyChanged` audit, vendor-sale anomaly detection in the sidecar.
|
||||||
|
|
||||||
|
**Beyond 1.0.** Phases above are the 1.0 read/event plane. Protocol 2.0's phasing (provisioning +
|
||||||
|
world-state boards) is [`PROTOCOL_2.md`](PROTOCOL_2.md) §13; Protocol 3.0's (visibility framework,
|
||||||
|
shard content and standings) is [`v3.md`](v3.md) §9, which also tracks what has landed. Shipped from
|
||||||
|
3.0 so far: **Part A** — the visibility framework — and **`world.ruleset`** ([`v3.md`](v3.md) §5),
|
||||||
|
`BridgeRuleset.cs`, the first bridge stream that is neither an event subscription nor a sweep: it is
|
||||||
|
emitted once per connect, like `server.hello`, because shard config changes only when an operator
|
||||||
|
edits a file.
|
||||||
|
|
||||||
### Config keys (`Config/Bridge.cfg`)
|
### Config keys (`Config/Bridge.cfg`)
|
||||||
|
|
||||||
```ini
|
```ini
|
||||||
@@ -328,6 +336,11 @@ EconomySweepSeconds=300
|
|||||||
|
|
||||||
Read in `Configure()` via `Config.Get<T>("Bridge.<Key>", default)`. Key scope is the filename: `Bridge.cfg` + `StatSweepSeconds` → `Bridge.StatSweepSeconds`.
|
Read in `Configure()` via `Config.Get<T>("Bridge.<Key>", default)`. Key scope is the filename: `Bridge.cfg` + `StatSweepSeconds` → `Bridge.StatSweepSeconds`.
|
||||||
|
|
||||||
|
The set above is the 1.0 sample, not the current one — every later phase added keys (sweep intervals
|
||||||
|
for each board, the town-crier/news caps, the admin write plane, account provisioning, and 3.0's
|
||||||
|
`RulesetEnabled` / `PublicConnectAddress` / `RulesetIncludeSchedule`). **`servuo-plugins/overlay/Config/Bridge.cfg`
|
||||||
|
is the authoritative, commented list**; `BridgeConfig.cs` holds the defaults.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. Phase 1 acceptance
|
## 11. Phase 1 acceptance
|
||||||
|
|||||||
@@ -285,6 +285,18 @@ City titles and faction/VvV merchant titles (`CityLoyaltySystem.ApplyCityTitle`,
|
|||||||
|
|
||||||
### 10.4 Factions / Vice vs Virtue
|
### 10.4 Factions / Vice vs Virtue
|
||||||
|
|
||||||
|
> **Status update (Protocol 3.0, 2026-07-28).**
|
||||||
|
>
|
||||||
|
> - **The deferred question is answered.** This shard runs **Vice vs Virtue** (`VvV.cfg Enabled=True`);
|
||||||
|
> old Factions is off, and in stock ServUO that is not a coincidence —
|
||||||
|
> `Services/Factions/Core/Faction.cs` sets `Settings.Enabled = !ViceVsVirtueSystem.Enabled`, so the
|
||||||
|
> two are mutually exclusive by construction. The `vvv.standings` / `vvv.battle` streams below are
|
||||||
|
> therefore unblocked, but are **not** scoped for 3.0 (see [`v3.md`](v3.md) §2 row 5).
|
||||||
|
> - **`world.systems` is superseded by `world.ruleset`** ([`v3.md`](v3.md) §5), which shipped in 3.0.
|
||||||
|
> It was never implemented under this name. `world.ruleset` carries the same
|
||||||
|
> `systems{cityLoyalty, vvv, factions, …}` sub-object this section asked for, plus the rest of the
|
||||||
|
> shard's published ruleset, so no orphan kind is left behind. Do not implement `world.systems`.
|
||||||
|
|
||||||
**Which system is live is a shard decision — verify before building.** Two exist:
|
**Which system is live is a shard decision — verify before building.** Two exist:
|
||||||
|
|
||||||
- **Old Factions** (`Scripts/Services/Factions`): `Faction.Commander` (leader, `Faction.cs:160`), `Faction.Election`, `Faction.Members` (`List<PlayerState>`), and faction-controlled **Towns** (`Town.cs` — each town has an owning faction, a sheriff, and finance). Config-gated and, on most modern shards, **off**.
|
- **Old Factions** (`Scripts/Services/Factions`): `Faction.Commander` (leader, `Faction.cs:160`), `Faction.Election`, `Faction.Members` (`List<PlayerState>`), and faction-controlled **Towns** (`Town.cs` — each town has an owning faction, a sheriff, and finance). Config-gated and, on most modern shards, **off**.
|
||||||
@@ -300,7 +312,10 @@ City titles and faction/VvV merchant titles (`CityLoyaltySystem.ApplyCityTitle`,
|
|||||||
{"kind":"vvv.standings","order":142000,"chaos":138500,"leaderSide":"Order"}
|
{"kind":"vvv.standings","order":142000,"chaos":138500,"leaderSide":"Order"}
|
||||||
```
|
```
|
||||||
|
|
||||||
> Start by detecting which system is enabled at boot and streaming only that one; emit a one-time `world.systems` frame (what's on: cityLoyalty, vvv, factions) so the website renders the right panels instead of guessing.
|
> Start by detecting which system is enabled at boot and streaming only that one. ~~emit a one-time
|
||||||
|
> `world.systems` frame (what's on: cityLoyalty, vvv, factions) so the website renders the right panels
|
||||||
|
> instead of guessing.~~ — **superseded: `world.ruleset` already carries that `systems` block** (see the
|
||||||
|
> status note at the top of this section).
|
||||||
|
|
||||||
## 11. Further integration points — a menu to pick from
|
## 11. Further integration points — a menu to pick from
|
||||||
|
|
||||||
|
|||||||
61
link/v3.md
61
link/v3.md
@@ -1,10 +1,23 @@
|
|||||||
# Protocol 3.0 — Shard content, standings & the visibility framework
|
# Protocol 3.0 — Shard content, standings & the visibility framework
|
||||||
|
|
||||||
**Status:** Planned, approved 2026-07-28. Not yet built. All work lands on an `edge` branch in each repo; `edge` → `main` is the v3 cutover.
|
**Status:** In progress. All work lands on an `edge` branch in each repo; `edge` → `main` is the v3 cutover.
|
||||||
**Date:** 2026-07-28
|
**Date:** 2026-07-28
|
||||||
**Codebase:** ServUO 57.4, `<servuo>`, net48 / x64, Expansion **EJ**.
|
**Codebase:** ServUO 57.4, `<servuo>`, net48 / x64, Expansion **EJ**.
|
||||||
**Companion to** [`PLAN.md`](PLAN.md) (1.0 read/event plane), [`PROTOCOL_2.md`](PROTOCOL_2.md) (2.0 provisioning + world-state streams), [`ADMIN_CONTROLS.md`](ADMIN_CONTROLS.md) (staff write plane), [`INTEGRATION.md`](INTEGRATION.md) (website API).
|
**Companion to** [`PLAN.md`](PLAN.md) (1.0 read/event plane), [`PROTOCOL_2.md`](PROTOCOL_2.md) (2.0 provisioning + world-state streams), [`ADMIN_CONTROLS.md`](ADMIN_CONTROLS.md) (staff write plane), [`INTEGRATION.md`](INTEGRATION.md) (website API).
|
||||||
|
|
||||||
|
### Progress
|
||||||
|
|
||||||
|
Each part is marked off here as it lands on `edge`. §9 carries the same state per sequencing row.
|
||||||
|
|
||||||
|
| Order | Part | State | Landed on `edge` |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | **A** — visibility framework + actor-leak fix (§3) | ✅ **Done** | website [#109](https://gitea.whitlocktech.com/RunicGateway/website/pulls/109) + [#110](https://gitea.whitlocktech.com/RunicGateway/website/pulls/110), docs [#64](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/64) + [#65](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/65) |
|
||||||
|
| 2 | **B/1** — `world.ruleset` (§5) | ✅ **Done** | PR-LINKS-B1 |
|
||||||
|
| 3 | **C** — spawn atlas (§6) | ⬜ **Next** | — |
|
||||||
|
| 4 | **B/2** — `points.board` (§7) | ⬜ Not started | — |
|
||||||
|
| 5 | **B/3** — `vendor.listing` (§8) | ⬜ Not started | — |
|
||||||
|
| 6 | **Cutover** — `PROTOCOL_VERSION` 2→3 (§4) | ⬜ Not started | — |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 1. Why 3.0
|
## 1. Why 3.0
|
||||||
@@ -64,7 +77,12 @@ independently of this work.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Part A — The visibility framework
|
## 3. Part A — The visibility framework ✅ Done
|
||||||
|
|
||||||
|
*Landed on `edge`: website [#109](https://gitea.whitlocktech.com/RunicGateway/website/pulls/109) (the framework)
|
||||||
|
and [#110](https://gitea.whitlocktech.com/RunicGateway/website/pulls/110) (the REST-projection gap §3.6.1
|
||||||
|
records), docs [#64](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/64) + [#65](https://gitea.whitlocktech.com/RunicGateway/docs/pulls/65).
|
||||||
|
Smoke-tested across all five rungs per §11.*
|
||||||
|
|
||||||
### 3.1 The leak this replaces (verified 2026-07-28)
|
### 3.1 The leak this replaces (verified 2026-07-28)
|
||||||
|
|
||||||
@@ -221,7 +239,28 @@ admin-set `uo_link_config.protocol` column — so it happens **exactly once**, a
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Part B/1 — `world.ruleset`
|
## 5. Part B/1 — `world.ruleset` ✅ Done
|
||||||
|
|
||||||
|
*Landed on `edge`. Implementation notes worth keeping:*
|
||||||
|
|
||||||
|
- ***`shadowguard` is derived, not configured.*** `Shadowguard.cfg` carries only `ReadyDuration` and
|
||||||
|
`RandomizeInstances` — there is no `Enabled` key — so the systems block reports `Core.TOL`
|
||||||
|
(the expansion gate) instead. Same shape for `factions`: `Factions.cfg` has no `Enabled` either, and
|
||||||
|
`Services/Factions/Core/Faction.cs` sets `Settings.Enabled = !ViceVsVirtueSystem.Enabled`, so the
|
||||||
|
frame reads that static rather than inventing a key. **Where a system's on/off state is derived, read
|
||||||
|
the system's own static; only read `Config.Get` where the .cfg key IS the truth.**
|
||||||
|
- **`caps.skill` / `caps.totalSkill` are in tenths** (1000 = 100.0), the way ServUO stores them.
|
||||||
|
Documented in `INTEGRATION.md` and converted in the client, because the raw number is actively
|
||||||
|
misleading rather than merely unhelpful.
|
||||||
|
- **`Config.Get` re-parses when the cached type differs.** `InternalGet<T>` caches the parsed value on
|
||||||
|
the entry and re-parses if `entry.Object is T` fails, so reading `PlayerCaps.SkillCap` as an `int`
|
||||||
|
where ServUO reads it as a `double` is correct (both parse) — it just re-parses. Harmless, but worth
|
||||||
|
knowing before assuming a shared cache.
|
||||||
|
- **The plugin CAN be compile-verified**, contrary to "no standalone build": point Roslyn
|
||||||
|
(`dotnet sdk/*/Roslyn/bincore/csc.dll`, `/langversion:7.3`, net48 reference assemblies) at the whole
|
||||||
|
ServUO `Scripts` tree with `overlay/Scripts/Custom/Bridge/*.cs` substituted for the deployed copy,
|
||||||
|
excluding `Scripts/obj` and `Scripts/bin`. 6,205 files, ~40 s, and it catches every signature error
|
||||||
|
a boot would. Worth doing before every plugin PR.
|
||||||
|
|
||||||
`PROTOCOL_2.md` §10.4 sketches a `world.systems` capability frame that was never implemented
|
`PROTOCOL_2.md` §10.4 sketches a `world.systems` capability frame that was never implemented
|
||||||
(`grep` returns nothing across all four repos). **`world.ruleset` subsumes it**, carrying a `systems`
|
(`grep` returns nothing across all four repos). **`world.ruleset` subsumes it**, carrying a `systems`
|
||||||
@@ -534,14 +573,14 @@ inherently up to one full cycle old, and the UI must say so.
|
|||||||
|
|
||||||
## 9. Sequencing
|
## 9. Sequencing
|
||||||
|
|
||||||
| Order | Part | Repos touched | Wire change |
|
| Order | Part | Repos touched | Wire change | State |
|
||||||
|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| 1 | **A** — visibility framework + actor-leak fix | website, docs | none |
|
| 1 | **A** — visibility framework + actor-leak fix | website, docs | none | ✅ Done |
|
||||||
| 2 | **B/1** — `world.ruleset` (§5) | all four | new kind |
|
| 2 | **B/1** — `world.ruleset` (§5) | all four | new kind | ✅ Done |
|
||||||
| 3 | **C** — spawn atlas (§6) | website, docs | none |
|
| 3 | **C** — spawn atlas (§6) | website, docs | none | ⬜ **Next** |
|
||||||
| 4 | **B/2** — `points.board` (§7) | all four | new kind + `char.profile` field |
|
| 4 | **B/2** — `points.board` (§7) | all four | new kind + `char.profile` field | ⬜ |
|
||||||
| 5 | **B/3** — `vendor.listing` (§8) | all four | new kinds |
|
| 5 | **B/3** — `vendor.listing` (§8) | all four | new kinds | ⬜ |
|
||||||
| 6 | **Cutover** — `PROTOCOL_VERSION` 2→3, `edge` → `main` | all four | the bump |
|
| 6 | **Cutover** — `PROTOCOL_VERSION` 2→3, `edge` → `main` | all four | the bump | ⬜ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ server/
|
|||||||
pages.router.js (2) /public/pages — the draft-preview
|
pages.router.js (2) /public/pages — the draft-preview
|
||||||
route precedes /:slug and is
|
route precedes /:slug and is
|
||||||
deliberately not site-mode gated
|
deliberately not site-mode gated
|
||||||
shard.router.js (13) /public/shard/* incl. the anonymous
|
shard.router.js (14) /public/shard/* incl. the anonymous
|
||||||
SSE stream; never site-mode gated
|
SSE stream; never site-mode gated
|
||||||
site.router.js (4) /settings /status /version /contact —
|
site.router.js (4) /settings /status /version /contact —
|
||||||
the group-root singletons; declares no
|
the group-root singletons; declares no
|
||||||
@@ -358,6 +358,23 @@ analogue to a password — and there is no hash-lookup constraint (verification
|
|||||||
unused rows and `bcrypt.compare`s each, like password verification). `used_at` is the single-use
|
unused rows and `bcrypt.compare`s each, like password verification). `used_at` is the single-use
|
||||||
marker. Cleared wholesale on TOTP disable / password change / password reset.
|
marker. Cleared wholesale on TOTP disable / password change / password reset.
|
||||||
|
|
||||||
|
### shard_ruleset — the shard's published ruleset (Protocol 3.0)
|
||||||
|
|
||||||
|
Singleton row (`id = 1`, CHECK-constrained) holding the latest `world.ruleset` frame: `rev`,
|
||||||
|
`expansion`, `payload` JSON (the whole frame), `t`, `updated_at`. The shard re-emits the complete
|
||||||
|
ruleset on every sidecar connect, so this is an **overwrite, not an append** — and the kind is
|
||||||
|
deliberately **not** in `LOGGED_KINDS`, since logging it would put a duplicate row in `shard_events`
|
||||||
|
on every reconnect while `server.hello` already marks each of those.
|
||||||
|
|
||||||
|
The frame is stored whole rather than normalized into columns: it is a flat description of server
|
||||||
|
config that is read as one page, so splitting it up would mean a schema change every time the shard
|
||||||
|
grows a new block. `rev` (the shard's FNV-1a of the body) and `expansion` are hoisted only because
|
||||||
|
they are cheap to display — the same payload-plus-hoisted-columns shape `shard_champs` uses.
|
||||||
|
|
||||||
|
**No row means the shard has never published one** (an older plugin, or `Bridge.RulesetEnabled=false`),
|
||||||
|
served as `null` rather than `{}`: "not published yet" and "published, everything off" are different
|
||||||
|
answers and the page renders them differently.
|
||||||
|
|
||||||
### shard_feature_visibility — per-feature audience config (Protocol 3.0)
|
### shard_feature_visibility — per-feature audience config (Protocol 3.0)
|
||||||
|
|
||||||
One row per shard feature: `feature` (PK), `enabled`, `audience` (a rung on the ladder in §6.5),
|
One row per shard feature: `feature` (PK), `enabled`, `audience` (a rung on the ladder in §6.5),
|
||||||
@@ -569,6 +586,7 @@ from the per-route **siteMode** middleware (§5), never from an auth gate.
|
|||||||
| GET | `/wiki` | list of pages (slug + title) |
|
| GET | `/wiki` | list of pages (slug + title) |
|
||||||
| GET | `/wiki/:slug` | single page |
|
| GET | `/wiki/:slug` | single page |
|
||||||
| POST | `/contact` | (rate-limited) send mail via SMTP; if unconfigured, respond `{fallback:"mailto", email}` |
|
| POST | `/contact` | (rate-limited) send mail via SMTP; if unconfigured, respond `{fallback:"mailto", email}` |
|
||||||
|
| GET | `/shard/ruleset` | the shard's own published ruleset (Protocol 3.0 `world.ruleset`): expansion, which optional systems are on, skill/stat caps, account and house limits, champion scroll rules, the save/restart schedule. Served from `shard_ruleset`, so it renders while the shard is down; live via `world.ruleset` on `/shard/stream`. Behind `requireFeature('ruleset')`. **`null`** means the shard has never published one — a real answer, distinct from a published ruleset. `caps.skill` / `caps.totalSkill` are in **tenths** (1000 = 100.0). |
|
||||||
| GET | `/shard/features` | the shard features **this caller** may reach plus the audience rung they resolved to (§6.5), so a client hides nav it can't follow. Reports only what the caller can see — the list itself never discloses a gated feature. Consumed by the SPA header and (pending) the Android nav. |
|
| GET | `/shard/features` | the shard features **this caller** may reach plus the audience rung they resolved to (§6.5), so a client hides nav it can't follow. Reports only what the caller can see — the list itself never discloses a gated feature. Consumed by the SPA header and (pending) the Android nav. |
|
||||||
|
|
||||||
Public content GETs pass through the **siteMode** gate (§5).
|
Public content GETs pass through the **siteMode** gate (§5).
|
||||||
|
|||||||
Reference in New Issue
Block a user