docs(rust): protocol 14 — kits.uses and reward notices (PROTOCOL §20, PLAN_REDESIGNS §2.9)

The wire half of PLAN_REDESIGNS §2 (D332, D333): PROTOCOL.md §2 says 14, and
§20 specifies kits.uses (counts only, bounded by the sync's bound, POST
/kits/uses) and perm.sync notices (the whole untold set, told once when the
player is awake, told keys kept on disk, noticesTold in the report).
PLAN_REDESIGNS §2.9 records the Rust half as built and its compile on both
rigs. Stacked on docs/event-step-editor (docs#335) for §2.8.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-10-12 02:48:12 -05:00
parent a2dbce1f89
commit 756c13d47b
2 changed files with 90 additions and 1 deletions

View File

@@ -409,6 +409,32 @@ Everything in §2.1–§2.3 and §2.5's core half, with three things settled whi
registers its 12 actions), the server suite 723/723 and the client suite 45/45 — the same numbers as 1.11.0's.
The Module-Rust half (the kit weekend, `linked`, `kits.uses`, the reward message) follows on `edge`.
### 2.9 As built: the Rust half, protocol 14 (2026-10-12)
Branches `feat/protocol-14-step-editor` in Rust-Plugins, Rust-Link and Module-Rust, into `edge`; the wire is
`rust-link/PROTOCOL.md` §20.
- **The kit weekend** is `rust.template.kit_weekend`: inputs kit, minutes (a duration), message and delivery.
It reads the kit from its server when it is filled in, so the lease names the kit's real permission. A kit
with no permission is open to everyone already and gets step 2 alone; a kit with no use limit has no
"one more use" and gets step 1 alone. The phase is the weekend: `advance: { after }` equals the minutes.
Run through the real 1.13.0 core (website#212), it registers, and the plan it builds validates as a spec.
- **`linked`** is "every linked account that has played on this server" — a stats row there — because a link
is fleet-wide and the server is what narrows it. It reads no tally, and past the server's recipient bound it
is refused with the bound's name (`EventsMaxRecipients`), never trimmed.
- **The reward message** is checked with the kit's name in place of `{kit}`, against the chat bound, so a
long kit name cannot push past what the game will say. It is stored on each grant row
(`rust_perm_run_grants.notice`, `notice_delivery`, `notice_told_at`).
- **The kit warning** is the `note` on `rust.options.grouppermissions` rows. That source is searchable, so the
editor may not hold the chosen row; core asks for that one value when it needs its note (website#212).
- **Timed params:** `rust.zone.open` and `rust.participation.open` declare `minutes` a duration, and the
group-permission lease gives `true` as its value example.
- **Compiled on both rigs, 2026-10-12.** Carbon 2.0.262 on Rust 25823813 (the latest of both): the bridge
compiled and loaded in 3.8 s, protocol 14, its told-notices store read at load. Oxide 2.0.7820 on the same
Rust build (the rig was behind on both, and was updated first: its first restart's steamcmd update did not
finish, the second did): compiled in 5.3 s and loaded. Both rigs were put back on their protocol-13 bridge afterwards, to match their sidecars until
the end-of-plan walk sets up protocol 14 (D331).
---
## 3. Zones: ZoneManager's options, and a dome (§4.4; D166, D167)

View File

@@ -50,7 +50,7 @@ it is listening without one.
## 2. Versioning
The wire version is a single integer — **13** as of the first player walk's fixes (§19) — declared in
The wire version is a single integer — **14** as of the step editor (§20) — declared in
**four** places that must agree:
| Where | Repo |
@@ -2638,3 +2638,66 @@ unaffected. The refusal is checked before anything is spawned, with the two reas
The site checks the same thing first, from the hello's `integrations.runicNpc`, and refuses the step before it
is sent; the bridge's check is for a site that missed the hello. `rg.npc` says the same when RunicNPC is not
loaded.
---
## 20. Protocol 14 — the step editor
Opened by [`PLAN_REDESIGNS.md`](../modules/rust/PLAN_REDESIGNS.md) §2 after protocol 13 shipped with RunicNPC
v1.0.0 (D332). It is built on `edge` in Rust-Plugins, Rust-Link and Module-Rust and released once, after the
end-of-plan walk (D331); the first-class optional plugins (§11 there) join it. **This section grows as the
pieces land.** `PROTOCOL_VERSION` becomes 14 in all four places (§2).
### 20.1 `kits.uses` — how many have used a kit up (§2.6)
The warning beside a kit weekend: *"Kit vip: 2 uses, 24 h cooldown — 9 of 41 linked players have used it up."*
The website asks about the players it already knows — the linked players of the server — and the answer is
**counts, never a list**.
```json
{"cmd":"kits.uses","reqId":"r-12","kit":"vip","steamIds":["76561190000000001","76561190000000002"]}
{"kind":"kits.uses","type":"reply","reqId":"r-12","kit":"vip","max":2,"cooldown":86400,"asked":2,"exhausted":1,"onCooldown":0}
```
- **Main thread**, two Kits reads per player (`GetPlayerKitUses`, `GetPlayerKitCooldown`), plus `GetKitMaxUses` and
`GetKitCooldown` once. `exhausted` counts players whose uses have reached `max`; a kit with no use limit
(`max` 0) is never used up. `onCooldown` counts players with any cooldown remaining. `asked` is how many ids
parsed.
- **Bounded by the sync's own bound** (`PermMaxOps`, 20,000): the site sends no more players than a sync
carries.
- Refusals are `kits.error`, as for `kits.list`: `kits-missing` (Kits is not loaded), `unknown-kit`,
`too-large`.
- **The sidecar route is `POST /kits/uses`**, a thin forward like `/kits`; the list rides in the body because it
can be as long as a server's linked players. Nothing is kept.
- **The website** asks once per kit that a permission gates, per server, at most 25 kits, and keeps the answer
a minute. It attaches the sentence as the `note` of every `rust.options.grouppermissions` row whose
permission is that kit's (core's option note, MODULE_API 1.13.0). A failure is no note.
### 20.2 `notices` on `perm.sync` — the reward's message (§2.4, D333)
"You earned kit vip — type /kit vip", said once to the player a reward step picked. `rust.kit.entitle` gains an
optional `message` and `delivery`; the site writes them on each grant row, and every sync carries the rows'
**whole untold set** for that server, beside `credits`:
```json
{"cmd":"perm.sync", …, "notices":[{"key":"7:3:11","steamId":"76561190000000001","message":"You earned kit vip — type /kit vip","delivery":"popup"}]}
```
- **`key`** is `<runId>:<stepId>:<userId>`: one reward step tells one person once, whichever of their linked
accounts it picked. **`steamId`** is that account, while it is still linked to the person.
- **When it is said is the plugin's.** At the end of the sync, to a player who is connected and **awake** —
a sleeping or loading player sees no popup and reads no chat — and otherwise on `OnPlayerSleepEnded`, the
next time they wake on this server. A popup goes through PopupNotifications when `delivery` is `popup` and
the plugin is loaded, and chat otherwise; the reward was granted long before this moment, so the player is
told either way. At most 2,000 notices per sync; a message is cut at the chat bound (`EventsMaxChat`).
- **Told keys are kept on disk** (`data/RunicGateway/notices.json`) until the site stops sending them, so a
reload between telling a player and the report that says so cannot tell them twice. Each sync forgets every
told key the set no longer carries.
- **The report lists them back**: `perm.report` gains `noticesTold`, every key of this sync's set that has been
told — at this sync or before it. Present only when the sync carried `notices`, like the credit counts. The
site marks those rows told, and they leave the next sync's set.
- **A withdrawn reward takes its message with it.** Teardown deletes the grant row, so the notice leaves the set
untold, and nobody is told about a reward that is no longer theirs.
- **Absent is not empty**, as for `credits`: a sync with no `notices` field leaves the plugin's set alone. The
site sends the field on every sync from protocol 14, empty when there is nothing to say. The notices are part
of the sync's digest, so a new one is pushed at once.