docs(rust): phase 13 plan, and 13a as built (PLAN.md §28, PROTOCOL.md §15) #268
@@ -999,7 +999,8 @@ Each phase ends with its findings written down, as every workstream here does.
|
||||
| 10 | **Notifications and engagement** (R7). ✅ **Built and walked 2026-09-23 — plan, as built and findings in §25 (D59–D68).** The criterion walked on the Oxide rig (two authorised owners alerted once each; bystander, raider and an unlinked owner told nothing), and the same plugin loaded on Carbon; a live walk found that every generic notice never said which server (§25.6). Streams, triggers with `ceiling` and `subjectKey`, audiences and engagement seeds — **the catalogue is §10, as corrected by §25.2**. The announce leg and the post hook are **not** in it (D62 amends R7), and protocol 7 widens the raid frame, which brings the two bridge repositories in | all 3 + docs | The offline raid alert reaches the player whose base it was, and nobody else |
|
||||
| 11 | **Android leg C** (R10). ✅ **Built and walked 2026-09-23 — plan, as built and findings in §26 (D69–D72).** The link check behind personal streams asked `module-uo` and locked the raid alert on every Rust site; it now asks the site's own module and holds back only push-on. A tickle is titled from the inbox row it names, and two links stopped opening the browser. **The relay hop was walked on `ntfy.sh` for the first time on any site**: a rig raid reached the emulator in 19 seconds, and after push was switched off in the app the next raid enqueued in-app only | Android-app + docs | A Rust notification arrives on a phone and can be switched off there |
|
||||
| 12 | **Events: option sources and the leases** (§9, **as corrected by §27**). ✅ **Built and walked 2026-09-24 on both rigs — as built and findings in §27.5–27.7.** 21 keys walked live, two `spawn.max_*` left out; two defects of its own fixed (the Oxide grant owner, `EventsEnabled` not applying); four found outside it and raised. [kit][kit] ch. 5's own ordering — leases before actions — and every key verified live before it is advertised. **Plan of record in §27 (D73–D79)**: the target names the server, game convars only (vanilla Rust has no gather/craft/smelt rate), the weekend-VIP lease is a group-wide permission, an `EventsEnabled` switch off by default, seven-day holds, and no budget dimension until phase 13; protocol 8 | Module-Rust + 2 + docs | A leased value is observed changing in the running game and restored, per key; `rust.group.permission` expires without core asking |
|
||||
| 13 | **Events: the actions** (§9, R3, R16). `rust.kit.entitle` first, then `rust.prefab.place` and `rust.announce`; `reversible: 'ledger'`; the kit option source flags kits with no permission gate, plus **`reconcile()` and the boot-id watch calling `ctx.events.reconcile()`** (§11.1) | all 3 + docs | A reward granted at 03:00 is waiting in the kit menu when the player next logs in, and a revert withdraws it; a wipe reconciles the ledger instead of stranding it |
|
||||
| 13a | **Events: the world verbs** (§9, R17, **split by D80**). **Plan of record in §28 (D80–D95).** The phase-7 owner fix (D85); `rust.zone.open` and `rust.prefab.place` (crates and NPCs, D88) at a monument or raw coordinates; the monument and prefab option sources; the plugin's ownership registry, keyed by the idempotency key; and **`reconcile()` with the boot-id and wipe watch calling `ctx.events.reconcile()`** (§11.1). Protocol 9 | all 3 + docs | A wipe reconciles the ledger instead of stranding it, and each world verb's teardown is observed in the game |
|
||||
| 13b | **Events: the rewards** (§9, R3, R16). The participation tally kept by the game (D81–D83), `rust.kit.entitle` through the site mirror's per-run rows (D84) with the kit option source flagging kits with no permission gate, and `rust.announce` with the announce leg (D90). Plan of record in §29, written before its code. Protocol 10 | all 3 + docs | A reward granted at 03:00 is waiting in the kit menu when the player next logs in, and a revert withdraws it |
|
||||
| 14 | **The live map** (R9). The map image over the bridge — request/reply, two-stage, one in flight, its own derivation version, no import on boot — plus the live layers and a per-layer public/players/admin switch built on **our own** visibility layer (§11.2 — `shardVisibility` is `module-uo`'s, not core's) | all 3 + docs | The map renders for the current wipe, and a player layer is invisible until an operator deliberately opens it |
|
||||
| 15 | **Android leg D** (R10). Map and events | Android-app | The map renders on a phone with the same layer gates |
|
||||
| 16 | **Discord slash commands** (R11). A small read-only set, every refusal deferred ephemeral | Module-Rust + docs | A refusal does not go public in the channel |
|
||||
@@ -1196,6 +1197,11 @@ its reasoning as the starting point rather than inventing a parallel set.
|
||||
> became the group-wide `rust.group.permission` (D75). Every lease's target names its server (D73).
|
||||
> And *"caps are per server"* holds only for a run an admin starts by hand: a scheduled run always
|
||||
> has the empty scope. The tables below are the plan as first written.
|
||||
>
|
||||
> **Corrected again by phase 13 (§28, 2026-09-24).** `rust.zone.open` is scheduled (13a, D86), a
|
||||
> location is a monument by kind and instance or raw coordinates (D87, D93), `prefab.place` covers
|
||||
> crates and NPCs with NPCs on their own `rust.npcs` budget (D88, D89), and a reward's recipients
|
||||
> come from a participation tally the game keeps (D81).
|
||||
|
||||
### Budgets — what core counts and bounds
|
||||
|
||||
@@ -4356,6 +4362,212 @@ the pair stayed granted (`already true`), because the site had asked for it.
|
||||
intended behaviour (`already true`), and the cause turned out to be the core cleanup finding above,
|
||||
not the plugin.
|
||||
|
||||
**Three of the four findings are filed against core** (2026-09-24): [website#205][w205] (cleanup
|
||||
restores an orphaned lease row), [website#206][w206] (`core.lease` drops `retry: false`) and
|
||||
[website#207][w207] (`core.lease`'s two calls under the default budget). The fourth, the phase-7
|
||||
mirror's owner bug, belongs to this module and is fixed in phase 13a (D85).
|
||||
|
||||
## 28. Phase 13 — the actions (plan of record, 2026-09-24)
|
||||
|
||||
The phase where an event makes something that did not exist. The row named three verbs, one option
|
||||
source and the reconcile watch. The decisions below made it two world verbs with NPCs, a monument
|
||||
source, a participation tally kept by the game, the entitlement, and the announce action with its
|
||||
leg. So it is **split in two**, the way Events phase 16 was (D80):
|
||||
|
||||
- **13a, the world**: the owner-bug fix, `rust.zone.open`, `rust.prefab.place`, the monument source,
|
||||
the plugin's ownership registry, and `reconcile()` with the boot and wipe watch. Protocol 9.
|
||||
- **13b, the rewards**: the participation tally, `rust.kit.entitle` with the kit source, and
|
||||
`rust.announce` with the announce leg. Protocol 10. It builds on 13a's zones. **Its plan of record
|
||||
is §29, written before its code**, the same way this section is.
|
||||
|
||||
Three repositories each time: [Rust-Plugins][rp], [Rust-Link][rl] and [Module-Rust][mr], into
|
||||
`edge`. This document goes into `main`. **Core is not expected to change and `MODULE_API` does not
|
||||
move.**
|
||||
|
||||
### 28.0 The decisions this phase needed
|
||||
|
||||
All from the org lead, 2026-09-24. D80–D91 cover both halves. D92–D95 came from the rig probe in
|
||||
§28.1 and apply to 13a.
|
||||
|
||||
| # | Decision |
|
||||
|---|---|
|
||||
| **D80** | **Phase 13 splits into 13a (the world) and 13b (the rewards)**, each with its own plan section, PRs and walk. 13a comes first because 13b's participation is counted inside 13a's zones. |
|
||||
| **D81** | **The recipients of a reward are counted by the GAME, not the website.** `rust.participation.open` / `.collect` follow `module-uo`'s shape: the plugin keeps a per-run tally and `collect` reports it to core on the envelope. Rejected (my recommendation): the module derives the tally from the connect and disconnect events it already ingests. That needs no plugin change, but it cannot see presence in a zone or kills credited in one, and it is only as complete as the sidecar's raw window. |
|
||||
| **D82** | **The tally's area is a zone when the step names one, and the whole server otherwise.** The zone is normally one the same run opened with `rust.zone.open`. It is fed by `OnEnterZone`/`OnExitZone` for zones in the tally only, so no other zone costs anything (R17's "subscribe selectively"). It is persisted, so a restart does not lose it. |
|
||||
| **D83** | **The score is set per event: seconds present, kills, or both.** It is chosen on the `participation.open` step. (The org lead's own answer, replacing the three options offered.) |
|
||||
| **D84** | **An earned entitlement is held by the site's permission mirror, in per-run rows.** It gets a table of its own keyed to the website user (D28) and unioned with admin grants into R2's desired set. A revert deletes only this run's rows, so the same kit granted by an admin or by another run stays. A wipe cannot lose it, because the site re-pushes. The cost is stated up front: **only a LINKED participant can receive one.** Unlinked participants are counted in the step's `detail` as missed. Rejected: the plugin grants the Steam id directly from a store of its own. That reaches unlinked players, but the site stops being the author of record. `rust_perm_grants` could not be reused as it stands: it is `UNIQUE (user_id, permission, scope)`, so an event's grant and an admin's grant of one kit are one row, and reverting the event's would delete the admin's. |
|
||||
| **D85** | **The phase-7 owner bug is fixed inside this phase**, as its own commit in 13a's Rust-Plugins PR. `EnsureGrant` and `EnsureGroupPermission` pass a `null` owner and read the grant back, so a grant that did not land is reported and not counted (§27.6). This section says plainly what the site was told wrongly from phase 7 until now. |
|
||||
| **D86** | **Both world verbs ship in 13a: `rust.prefab.place` and `rust.zone.open`.** No phase row had scheduled `zone.open`, although R17 moved it into the base catalogue. Rejected: deferring it to phase 14 with the map. |
|
||||
| **D87** | **A location is a monument (plus an optional offset) or raw world coordinates**, one or the other on each step. |
|
||||
| **D88** | **`prefab.place` may spawn crates and NPCs, and never vehicles.** Vehicles were rejected because a player can take one, and a revert would kill something a person may be sitting in. The allowlist lives in the plugin, and neither the sidecar nor the site can widen it. |
|
||||
| **D89** | **NPCs are counted on their own budget dimension**, `rust.npcs`, apart from `rust.prefabs`, the way `module-uo` separates creatures from decor. An operator can then allow crates freely and cap fights. 13a declares `rust.prefabs`, `rust.npcs` and `rust.zone.minutes`; 13b declares `rust.grants` and `rust.announcements`. This is D79's "each is declared beside the verb that costs it". |
|
||||
| **D90** | **`rust.announce` ships with the announce leg** (D62): one chat verb in the plugin serves both. |
|
||||
| **D91** | **Phase 12's three core findings are filed**: website#205, #206 and #207. |
|
||||
| **D92** | **Zones are re-created from the plugin's registry.** ZoneManager keeps temporary zones in memory only, and **erases every zone a plugin owns when that plugin unloads** (§28.1). The bridge keeps its zones in its own file and re-creates them in three cases: on its own load, on `OnPluginLoaded(ZoneManager)`, and after a restart. It does this within the same wipe only; a new wipe drops them all. A zone therefore lasts until the run reverts it. Rejected: reporting the zone lost and letting reconcile orphan it. |
|
||||
| **D93** | **A monument is named by kind and instance, and resolved when the step runs.** The value is `<serverId>/<prefab short name>`, and `#n` picks the n-th of a kind that repeats (`srv-a/harbor_1#2`), numbered in a stable order. A step naming a unique monument therefore survives a wipe onto a new procedural map. A kind the new map does not have is refused `retry: false`, with the reason. The source lists what this wipe's map actually has. Rejected: any instance picked at random, and a pinned position (every saved event would break at the next wipe). |
|
||||
| **D94** | **`EventsEnabled` gates the world verbs too.** It is one consent: *events may change this world unattended*. As with D76, revert, reconcile and listing always work, so switching events off never strands a crate, an NPC or a zone. |
|
||||
| **D95** | **Per-step bounds: 25 crates, 20 NPCs.** A 50 m scatter, a monument offset of up to 150 m, a zone radius of 5 to 150 m, and raw coordinates that must lie on this map. They are **refused, never clamped** (link v6 §8.2's rule). They are plugin config that an operator may lower, and the site mirrors them so a dry run shows them. (The org lead chose these over the 10/10 I recommended.) |
|
||||
|
||||
### 28.1 Facts 13a rests on
|
||||
|
||||
**From the Oxide rig**, 2026-09-24, found with a probe plugin (`RgProbe13`, rig-only, never
|
||||
shipped):
|
||||
|
||||
- **The map reports 85 monuments, and their names repeat.** There are two Harbors (`harbor_1`,
|
||||
`harbor_2`), two Fishing Villages, two Abandoned Supermarkets, seven Train Tunnels, five Water
|
||||
Wells and about thirty Substations. Each `MonumentInfo` carries a prefab path, a display phrase, a
|
||||
tier, a type and bounds. A display name therefore cannot be an option value, which is why D93
|
||||
exists. The prefab short name is unique per kind, and the instance number separates repeats.
|
||||
- **The allowlist candidates exist, taken from `GameManifest.Current.entities` rather than from
|
||||
memory**: 51 crate prefabs, 25 `ScientistNPC_*`, `NPC_Bandit_Guard`, the tunnel and underwater
|
||||
dwellers, `codelockedhackablecrate.prefab` and `supply_drop.prefab`. Each one the plan names below
|
||||
was spawned at the Power Plant and reported its type: crates `LootContainer`, the hackable crate
|
||||
`HackableLockedCrate`, the supply drop `SupplyDrop`, the scientists `ScientistNPC`.
|
||||
- **A restart is not proof that a placed object is gone, and that is the opposite of UO's town
|
||||
crier.** Everything was spawned, then `server.save`, then a restart. The crates, the hackable
|
||||
crate, the supply drop and the loot barrel were all **still there with the same net id**:
|
||||
`enableSaving` is true on each. All three scientists were **gone**: `enableSaving` is false on
|
||||
NPCs. So `reconcileByBootId`'s trick is wrong for a crate and right for an NPC. **Reconcile asks
|
||||
the plugin by net id**, and the plugin answers what the world holds. Only a **wipe** proves that
|
||||
everything is gone.
|
||||
- **ZoneManager keeps temporary zones in memory, and its `OnPluginUnloaded` erases every zone the
|
||||
unloading plugin owns** (`ZoneManager.cs` 3.1.14, line 92). An `oxide.reload RunicGateway`, which
|
||||
every framework update and every hand reload causes, would silently delete every event zone in the
|
||||
middle of a run. A reload of ZoneManager itself loses them too, because the whole dictionary goes.
|
||||
That is D92.
|
||||
- **`EraseTemporaryZone(owner, id)` erases an unowned zone and returns `true`** (§12.4, still so in
|
||||
3.1.14). The bridge's own registry decides what it may erase, and never ZoneManager's answer.
|
||||
|
||||
**From the bridge** (`Rust-Plugins`, `edge`):
|
||||
|
||||
- **There is no at-most-once store.** UO's shard answers a repeated key with the original reply,
|
||||
and this plugin has nothing like it. Without one, a `prefab.place` retried after a lost answer
|
||||
places everything twice. **The ownership registry is therefore keyed by core's idempotency key**,
|
||||
and a repeat of a key answers with the net ids the first call made. The same record answers a
|
||||
`revert` that arrives with no resources and only the key: *everything placed under that key*.
|
||||
- **The mirror's owner bug is live** (`RunicGateway.cs` 3144 and 3179, `this` in both).
|
||||
|
||||
**From core**: an action is handed `{ runId, stepId, idempotencyKey, scope, params, actor, verify }`
|
||||
and never the run's participants, which is why D81 needs a tally at all. The timeouts are the
|
||||
sidecar reply at 10 s and this module's client at 12 s, so every 13a verb declares
|
||||
`budgetMs: 15000`.
|
||||
|
||||
### 28.2 The 13a catalogue
|
||||
|
||||
**Budgets** (D89): `rust.prefabs` (crates placed, count), `rust.npcs` (NPCs placed, count) and
|
||||
`rust.zone.minutes` (zone time held, minutes).
|
||||
|
||||
**Option sources**:
|
||||
|
||||
| Source | Values | Filled from |
|
||||
|---|---|---|
|
||||
| `rust.options.monuments` | `<serverId>/<kind>` or `<serverId>/<kind>#<n>` | each configured server's live monument list, grouped by server. The label is the display phrase plus a grid square, so two Harbors can be told apart |
|
||||
| `rust.options.prefabs` | an allowlist key (`crate.elite`, `npc.scientist.heavy`, …) | the plugin's allowlist, grouped **Crates** / **NPCs** |
|
||||
|
||||
**Actions** (all `risk: 'change'`, `reversible: 'ledger'`, `budgetMs: 15000`):
|
||||
|
||||
| Action | Params | Ledger | `cost()` |
|
||||
|---|---|---|---|
|
||||
| `rust.zone.open` | `server`, a location, `radius`, `name`, optional `minutes` | one resource: the zone | `{ 'rust.zone.minutes': minutes }` when given |
|
||||
| `rust.prefab.place` | `server`, a location, `prefab`, `count`, optional `spread` | one resource **per net id** | `{ 'rust.prefabs': count }` or `{ 'rust.npcs': count }` by the prefab's kind |
|
||||
|
||||
A **location** is `monument` (from the source) with an optional `offsetX`/`offsetZ` in metres, **or**
|
||||
`x`/`z` (and an optional `y`, otherwise the terrain height). A step that gives both kinds, or
|
||||
neither, is refused `retry: false`.
|
||||
|
||||
- **A step names its server as a param, the way a lease names it in its target (D73).** A scheduled
|
||||
run's scope is always `''` (§27.1), so the scope cannot carry it.
|
||||
- **The allowlist as planned**, trimmed further if the walk cannot see any of it working:
|
||||
- Crates: `crate.basic`, `crate.normal` (military), `crate.normal2`, `crate.elite`, `crate.tools`,
|
||||
`crate.hackable`, `supply.drop`, `barrel.loot`.
|
||||
- NPCs: `npc.scientist` (roam), `npc.scientist.heavy`, `npc.scientist.tethered`,
|
||||
`npc.bandit.guard`.
|
||||
- **One resource per net id**, as UO files one per serial. A group of which players looted half
|
||||
then reconciles per crate, and not all or nothing.
|
||||
|
||||
### 28.3 What 13a builds
|
||||
|
||||
**Rust-Plugins**: protocol 9, and `overlay.toml`'s protocol advanced to match.
|
||||
|
||||
- **The owner fix (D85), first and as its own commit.** A `null` owner, and a read-back after every
|
||||
grant so the report counts only what the store holds.
|
||||
- **`world.zone`, `world.place`, `world.revert`, `world.owned` and `world.monuments`.**
|
||||
- **Every write is gated by `EventsEnabled`** (D94) and bounded by config (D95). The refusal names
|
||||
the switch or the bound.
|
||||
- **A monument resolves when the step runs** (D93), from `TerrainMeta.Path.Monuments` in a stable
|
||||
order (prefab path, then position). The ground height comes from `TerrainMeta.HeightMap`.
|
||||
- **Raw coordinates are refused off the map** (`World.Size`).
|
||||
- **The ownership registry** is a file under the framework's data directory, beside the lease
|
||||
holds. Each entry records the run id, the idempotency key, the kind (zone, crate, NPC), the net id
|
||||
or zone id, the prefab key, where it was put, the `bootId` and the `wipeId`.
|
||||
- **A repeated key answers with the original ids** and places nothing (§28.1).
|
||||
- **`world.revert`** kills what the registry says this run owns: every net id or zone id asked
|
||||
for, or, given only a key, everything under it. An id that is already gone is a success. **An
|
||||
id the registry does not hold is `refused`**, never erased, whatever ZoneManager would allow
|
||||
(§12.4).
|
||||
- **`world.owned`** answers what the world still holds, checked live:
|
||||
`BaseNetworkable.serverEntities.Find(net id)` for an entity, the registry and `CheckZoneID` for
|
||||
a zone. Any entry whose thing is gone is pruned as it walks, as UO's does.
|
||||
- **Load**: on the same boot, nothing changes. After a restart, NPC entries are gone, found by
|
||||
looking (the game does not save them, §28.1), and crates are still there. After a new wipe, the
|
||||
whole registry is dropped and everything reads gone.
|
||||
- **Zones (D92)**: each zone is created with the bridge as `owner` and an id of its own naming
|
||||
(`rg-<run>-<n>`). It is re-created from the registry on the bridge's load, on
|
||||
`OnPluginLoaded(ZoneManager)` and after a restart within the same wipe. ZoneManager being absent
|
||||
is refused with the reason, as every base-plugin dependency is.
|
||||
- **Carbon**: nothing conditional is expected. `GameManager`, `BaseNetworkable` and `TerrainMeta`
|
||||
belong to the game, and ZoneManager is the same `.cs` file on both frameworks. The walk proves it.
|
||||
|
||||
**Rust-Link**: protocol 9. `POST /world/zone`, `POST /world/place`, `POST /world/revert`,
|
||||
`GET /world/owned?runId=` and `GET /world/monuments`, stamped and forwarded as the lease routes are.
|
||||
The sidecar knows nothing about prefabs, bounds or monuments.
|
||||
|
||||
**Module-Rust**:
|
||||
|
||||
- `registerEventBudgets` (the three above), `registerEventActions` (the two above) and two more
|
||||
`registerEventOptionSources`.
|
||||
- `perform` validates everything a dry run can check without the game: the server is configured,
|
||||
exactly one kind of location, the bounds (mirrored), and the prefab is in the allowlist. It
|
||||
answers `verify` there, then sends the key through.
|
||||
- `revert` sends **no key of its own** (MODULE_API §2.4's `module-uo` lesson: the undo must not
|
||||
carry the key the do went out under), and treats `refused` as `failed`. `reconcile` asks
|
||||
`world.owned` and answers `inForce`. Anything unanswerable leaves the group alone.
|
||||
- **The watch**: the server refresh already writes each server's `bootId` and `wipeId`. A change in
|
||||
either calls `ctx.events.reconcile()`, and the log line says which. The first sighting after the
|
||||
module boots is not a change, because core's own boot reconcile covers it.
|
||||
- The mirror's report (`permSync`) gains the plugin's read-back count, so the admin page shows
|
||||
*"not landed"* where it used to show *applied*.
|
||||
- `ci/bundle.json` gains the new file, and the frozen manifest is regenerated if it moves. No new
|
||||
route, and no swagger change is expected.
|
||||
|
||||
**docs**: this section as built, the phase rows, §9's catalogue corrected, and
|
||||
`rust-link/PROTOCOL.md` §15 for protocol 9.
|
||||
|
||||
### 28.4 13a is done when
|
||||
|
||||
The row's second half, with D86 added: **a wipe reconciles the ledger instead of stranding it, and
|
||||
each world verb's teardown is observed in the game.** Walked on the Oxide rig against real core, with
|
||||
event runs authored in the admin UI:
|
||||
|
||||
1. **Each verb, observed.** A zone opened at a monument with an offset: `GetZoneIDs` lists it, and
|
||||
`IsPlayerInZone` is true for a player moved into it. Crates and NPCs placed at a monument and at
|
||||
raw coordinates are found by net id where they were put. At teardown every one is gone and every
|
||||
row reads `reverted`.
|
||||
2. **The lost answer.** A dispatch whose reply is dropped, then retried with the same key, places
|
||||
nothing new. A `revert` with no resources and only that key removes what was placed.
|
||||
3. **Reload and restart.** An `oxide.reload` of the bridge mid-run, and one of ZoneManager: the zone
|
||||
comes back. A server restart mid-run: the watch calls reconcile, crates stay `confirmed`, NPCs go
|
||||
`orphaned`, and the zone comes back.
|
||||
4. **The wipe.** A wipe mid-run (a new save on the rig): the watch calls reconcile, every row goes
|
||||
`orphaned`, teardown does nothing harmful, and nothing is placed on the new map.
|
||||
5. **The switch and the bounds.** With `EventsEnabled` off, a place is refused with the switch
|
||||
named, and reverting an existing run still works. 26 crates is refused on the authoring form and
|
||||
by the plugin.
|
||||
6. **The owner fix.** A site grant of a permission another plugin registered lands in Oxide's
|
||||
store, and the report counts it. Before the fix it read *applied* and held nothing.
|
||||
7. **Carbon.** At least one of each verb, with reload and restart, on the Carbon rig. If that cannot
|
||||
be done, this section says so.
|
||||
|
||||
---
|
||||
|
||||
[aa]: https://gitea.whitlocktech.com/RunicGateway/Android-app
|
||||
@@ -4365,5 +4577,8 @@ the pair stayed granted (`already true`), because the site had asked for it.
|
||||
[rl]: https://gitea.whitlocktech.com/RunicGateway/Rust-Link
|
||||
[rp]: https://gitea.whitlocktech.com/RunicGateway/Rust-Plugins
|
||||
[mr]: https://gitea.whitlocktech.com/RunicGateway/Module-Rust
|
||||
[w205]: https://gitea.whitlocktech.com/RunicGateway/website/issues/205
|
||||
[w206]: https://gitea.whitlocktech.com/RunicGateway/website/issues/206
|
||||
[w207]: https://gitea.whitlocktech.com/RunicGateway/website/issues/207
|
||||
|
||||
[kit]: https://gitea.whitlocktech.com/RunicGateway/Integration-kit
|
||||
|
||||
Reference in New Issue
Block a user