docs(rust): the RaidableBases spike, and D203-D205

The free edition (3.2.0) on both rigs with CopyPaste: its public API, the
17-argument lifecycle hooks, spawning only through `rbevent` at a spot the
plugin picks, base ids always "0", despawn only all-at-once, the boot
hitch and grid wait, and the profile options. Section 11.4.2 is left for
the paid edition the org lead supplies later in the week.

D203 RaidableBases picks where; D204 a helper despawns only the event's
base; D205 profile + difficulty + per-event overrides, and the site edits
RaidableBases' profiles as a named exception.

Refs RunicGateway/Module-Rust#21

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
2026-09-28 11:56:38 -05:00
parent 5328ff35b5
commit 6684ea5c60

View File

@@ -1,7 +1,7 @@
# `module-rust` — the redesigns, planned
**Status:** plan, written 2026-09-27. **Its eleven questions were answered the same day: D188–D198 (§10).**
On 2026-09-28 the org lead added the first-class optional plugins (§11, D199–D202).
On 2026-09-28 the org lead added the first-class optional plugins (§11, D199–D202), and after the RaidableBases spike, D203–D205 (§11.4).
It is step 3 of [`PLAN_FIXES.md`](PLAN_FIXES.md) §6: the six changes the org lead decided during the first
player walk, each "planned in detail before code". The org lead asked for all six in one plan (2026-09-27), so
they can be read together. No code has been written for any of them yet.
@@ -632,6 +632,9 @@ The org lead added §11 on 2026-09-28 and answered its questions the same day:
| **D200** | **Economics: all four uses.** Coins as an event reward (withdrawn on revert); balances shown to the player (site and app) and to staff; staff deposit, withdraw and set a balance, with an activity row; and a *richest* leaderboard column and chat-title condition. | — | 11.2 |
| **D201** | **Backpacks: staff view any player's backpack, a player views their own (site and app), and an event can grant extra capacity for a time**, taken back at the end. | Showing backpack sizes as named tiers on the permission screen. | 11.3 |
| **D202** | **RaidableBases: all four uses, and the paid edition too.** Active bases on the live map and in the feed; an event step that spawns a base at a difficulty and despawns it at the end; bases raided per player on the leaderboard and as a title condition; and the paid edition's extras (five difficulties, lockouts) where it is installed, falling back to what both editions share. | Supporting only what the free edition has. | 11.4 |
| **D203** | **RaidableBases picks where an event's base goes**; the site learns the spot from `OnRaidableBaseStarted` and shows it. The paid edition's placement, if it has one, is offered where installed. | A helper that places a base at a chosen monument. | 11.4.3 |
| **D204** | **At an event's end, a helper (`RunicGatewayRaids.cs`, D168) despawns only that event's base.** Without the helper the base despawns on its own timer, and the run records it as expiring on its own. | Leaving every base to its timer; RaidableBases' "despawn all". | 11.4.3 |
| **D205** | **An event picks a RaidableBases profile and a difficulty, may override a bounded set of settings for that spawn (through the helper), and the site edits RaidableBases' profiles** — a named exception to the editor never walking a data directory. | Profile and difficulty only; overrides without profile editing. | 11.4.3 |
## 11. First-class optional plugins (D199–D202)
@@ -702,6 +705,129 @@ Personal GitHub mirrors are not a source: k1lly0u's mirror of Kits is two major
- **The spike first:** pull the free source, confirm every hook and API signature above, and find the listing
and despawn calls. Then load it on both rigs, with TruePVE, and raid one base.
#### 11.4.1 The free edition, as the spike found it (2026-09-28)
The source is RaidableBases 3.2.0 (free, `umod.org/plugins/RaidableBases.cs`), and it was loaded on both rigs
with CopyPaste 4.3.0. A throwaway spike plugin made one base file from the walk hut and logged every
lifecycle hook. It was deleted afterwards, and so were the three plugins on both rigs.
**What it needs.** **CopyPaste is a hard dependency.** A base is a CopyPaste building file in
`data/copypaste/`, and **none ship with the free edition**: the default profile `RaidBases` names
`RaidBase1`–`RaidBase5`, and none of them exist. A base with no storage box is thrown away as soon as it is
pasted ("No usable boxes found"). A PvE plugin (TruePVE, SimplePVE, NextGenPVE, Imperium or AegisPVE) is
optional.
**What it costs at boot.** `OnServerInitialized` took 6.7 s (Oxide) and 7.9 s (Carbon) on the main thread.
The spawn grid then builds for about a minute: 52 s for 1,172 points on the rigs' 3000 map. **During that
minute a spawn is refused** ("grid is loading").
**Its public API** (`[HookMethod]`):
| Call | Returns | Use to us |
|---|---|---|
| `IsPremium()` | `false` on the free edition | hello's edition field |
| `GetAllDifficulties()` | `[("Normal", 512)]` on the free edition | the step's difficulty list, read from the installed plugin rather than hard-coded |
| `GetAllEventsCount()`, `GetActiveEventCount()` | ints | the servers page |
| `GetAllEvents()` / `GetAllEventsNonAlloc(list)` | an 18-field tuple per base: position, mode, level, PvP, owner, raiders, intruders, entities, base name, spawn and despawn time, radius, loot left | the live map layer. **The plugin warns that `GetAllEvents` changes shape in the next update.** |
| `EventTerritory(pos)`, `…Any`, `…All`, `GetPlayersFrom`, `GetOwnerFrom`, `HasPVPDelay` | | not needed |
**Nothing in the API spawns, despawns, or lists profiles.**
**The hooks it raises.** `OnRaidableBaseStarted`, `Ended`, `Completed`, `Despawn`, `Despawned` and
`PrivilegeDestroyed` all carry the same 17 arguments:
```
(Vector3 location, int level, bool allowPVP, string id, float, float, float loadTime, ulong ownerId,
BasePlayer owner, List<BasePlayer> raiders, List<BasePlayer> intruders, List<BaseEntity> entities,
string baseName, DateTime spawn, DateTime despawn, float protectionRadius, int lootRemaining)
```
A plugin method with exactly that signature binds on both frameworks. `Started` also has a one-argument
overload carrying the plugin's own class, which the bridge cannot name and does not need. Other hooks:
`OnPlayerEnteredRaidableBase` / `Exited` (14 arguments, the player first), `OnRaidableBaseUnlocked`,
`OnRaidableAwardGiven` / `Owner`, the PvP-delay hooks and loot hooks.
**How another plugin spawns a base: only through the server console.**
- `rbevent [baseName]` queues a base, and **RaidableBases chooses where**. On the rigs, three spawns landed at
Q6, C13 and C4, and none of them was named by the command. There is no position, no monument and no
difficulty argument, and **nothing comes back** to the caller.
- `rbe` pastes where a player is looking, so it needs a player in the game.
- **Every base's `id` is `"0"`** on the free edition. The only way to know which base the event made is the
next `Started` for that base name after the command. That is safe while events make one base at a time.
**How another plugin despawns a base: all or nothing.** `rbevent despawnall` removes every base on the
server, including ones the event did not make, and `rbevent despawn_inactive` removes every inactive one.
Despawning **one** base needs a player standing at it. Left alone, a base despawns on its profile's own
timer: 45 minutes in the default profile, as `despawn` in `Started` said.
**Its options.** Each profile has well over a hundred settings. Among them: NPCs, turrets, loot rules,
despawn timers, the plugins to block, where bases may spawn (beaches, roads, monuments), what players may
do, and backpacks. The global config has its own schedulers (Maintained, Scheduled and Manual events), map
markers, ZoneManager zones, a ranked ladder and messages. **Profiles live in the plugin's data directory**,
which the site's configuration editor deliberately never walks. The global config is in the config
directory, where the editor already reaches it.
**What that means for D202's event step.** "Spawn a base at a difficulty near a monument, and despawn it at
the end" is not possible on the free edition through anything RaidableBases offers another plugin. What
it does offer is: spawn a named base, somewhere, now; learn where and when it will despawn from the hook;
and remove every base at once. §11.4.3 asks which way to go.
#### 11.4.2 The paid edition — to be filled when the licensed copy arrives
The org lead supplies a licensed copy later in the week of 2026-09-28. The same spike runs against it on
both rigs, and this section records:
- `IsPremium()` and `GetAllDifficulties()` (expected: five tiers);
- whether it adds a spawn at a position, a monument or a difficulty, a despawn of one base, or real base
ids — by API or console command;
- whether the 17-argument hooks and `GetAllEvents` have the same shape (the free edition warns that the
latter is changing), and what `level` carries per difficulty;
- lockouts, buyable events and the ranked ladder: the hooks they raise and what the feed could say;
- anything in it that needs a D168 helper, and anything the free edition's answer in §11.4.3 would get
wrong for it.
The design in §11.4.3 is written so the paid edition extends it rather than replaces it. The step reads
what the installed edition offers — difficulties, and any placement or despawn it supports — and offers
exactly that.
#### 11.4.3 Decided (2026-09-28)
The org lead answered the spike's three questions the same day:
- **D203: where a base goes — RaidableBases picks.** The step spawns the chosen base with `rbevent`, and
RaidableBases places it wherever its grid allows. The bridge learns the spot from the next
`OnRaidableBaseStarted` for that base name. The site then shows it on the live map and in the run log, and
records it on the run's ledger. If the paid edition offers placement, the step offers it there too
(§11.4.2).
- **D204: at the end — a helper removes only the event's base.** Teardown, a revert, and an operator's stop
each despawn exactly the base the run made, never another. The free edition cannot do that from outside
(§11.4.1), so a D168 helper, **`RunicGatewayRaids.cs`**, finds the run's base by the position and spawn time
`Started` reported, and calls RaidableBases' own despawn on that one base. Like the zone helper
(D181, D182), it is optional and default-installed, and it reports at hello. **Without it, the step still
spawns, and the base despawns on its own timer.** The site says so on the step, and the run records the
base as expiring on its own, like D170's zones. A base the plugin despawns first is marked `expired`.
- **D205: what an event can choose — a profile, a difficulty, and per-event overrides; and the site edits
profiles.**
- **Profile and difficulty.** The step picks a RaidableBases profile, and a difficulty from the installed
plugin's `GetAllDifficulties()`. The free edition offers "Normal"; the paid edition offers its five.
- **Per-event overrides.** The step may override a bounded set of a profile's settings for this spawn
only. The first set is PvP on/off, despawn minutes, NPCs on/off and count, and the lock-to-first-attacker
rule; the build's spike settles which of them the plugin lets a helper change after `Started`. The free
API has no way to do this, so the helper applies them. **Without the helper there are no overrides**, and
the step says so.
- **The site edits profiles.** The configuration page gains RaidableBases' profile files,
`data/RaidableBases/Profiles/*.json`. This is a **named exception** to the rule that the editor never
walks a plugin's data directory, which exists because that directory holds live state and the
permission stores. The exception is that one folder, for that one plugin. It gets the editor's usual
guarantees: the write is recorded, a reload is watched, and a failed load rolls back (D178, D179). The
reload is `rb.reloadprofiles`, which the spike ran on both rigs, not a plugin reload.
**Still to settle in the build's own spike:** exactly which internal routine the helper calls to despawn one
base, and which settings it can override after `Started`, on both editions. Also the one-at-a-time rule: the
step refuses to spawn a second base of the same name on a server while a first one is waiting to start,
because the free edition's bases have no ids to tell them apart.
### 11.5 Protocol
These are new verbs, events and hello fields. If they are built before the cutover they join the unreleased