Compare commits

..

3 Commits

Author SHA1 Message Date
664d2b2144 docs(rust): D206-D208, the org lead's answers to the helper spike
Lock-to-first-attacker per event via a BypassUseOwners patch; a step places
its base where RaidableBases picks or at an admin-given spot with a height
adjustment or exact height; a blocked spot is refused with the reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-28 12:20:30 -05:00
a34b5fdbf0 docs(rust): record the RaidableBases helper spike (§11.4.4)
One base despawns alone; overrides go in at OpenEvent through a deep copy;
lock-to-first-attacker is main-config only; an admin-given spot and height
work through a copied profile (Clone() is shallow and leaked); the area
check misses water; reloads drop Harmony patches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-28 12:18:33 -05:00
6684ea5c60 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
2026-09-28 11:56:38 -05:00

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 spikes, D203–D208 (§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,12 @@ 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 |
| **D206** | **Lock-to-first-attacker is set per event.** RaidableBases keeps it only in its main config, so the raids helper patches the base's `BypassUseOwners()` to answer from the run's own setting for the event's base, and leaves every other base to the config. | One server-wide switch the config page edits; no lock control at all. | 11.4.5 |
| **D207** | **A step places its base either where RaidableBases picks (the default, D203) or at an admin-given spot:** x and z, plus either a height adjustment or an exact height. The spot needs the raids helper; without it the step offers only the plugin's pick. | Only an admin-given spot; keeping D203 alone. | 11.4.5 |
| **D208** | **A blocked spot is refused, with the reason.** The step does not spawn when the plugin's area check fails or the helper's water and terrain test fails, and the run log says why ("player building", "raid base", "in water"). The editor runs the same check when the step is saved. | Falling back to the plugin's pick; spawning anyway. | 11.4.5 |
## 11. First-class optional plugins (D199–D202)
@@ -702,6 +708,209 @@ 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:** the same helper questions on Carbon and on the paid edition.
Also the one-at-a-time rule: while a first base of the same name is still waiting to start on a server, the
step refuses to spawn a second one, because the free edition's bases have no ids to tell them apart.
§11.4.4 may retire that rule.
#### 11.4.4 The helper spike (2026-09-28)
The org lead asked for two tests of a D168 helper before the build: can it despawn one base, and which
settings can it override? A third question came with them: can an admin give the spot, meaning x, z and a
height adjustment? The spike ran on the rust-oxide rig against the free edition, 3.2.0. Carbon and the paid
edition are still to run (§11.4.2). Every finding below reaches RaidableBases' internals by reflection and a
Harmony patch. None of it is public API, so the helper has to check the plugin's version at hello, as the
zone helper does.
- **Despawning one base works.** The plugin keeps its live bases in a list on the plugin, and each base has
its own `Despawn()`. Calling it on one base removed that base and no other. It fired the usual
`OnRaidableBaseDespawn` and `OnRaidableBaseDespawned` hooks, so the bridge's feed sees nothing new.
- **The helper can know exactly which base is the event's.** A Harmony postfix on the plugin's `OpenEvent`
receives the new base object itself. The helper can tag it for the run at birth, instead of matching the
position and spawn time `Started` reports afterwards (D204). The one-at-a-time rule above may then go,
if the build confirms it on Carbon and the paid edition.
- **Overrides go in at `OpenEvent`, not after `Started`.** At `OpenEvent` the base holds a reference to its
profile's settings object, which every base of that profile shares. The helper swaps in a deep copy (a
JSON round trip), changed for this spawn. The shared profile was checked afterwards and was untouched.
Proven on the rig:
- PvP on/off;
- despawn minutes (inactive and total, via the profile's own "override config" switch);
- NPCs on/off, and the scientist and murderer counts.
- **Lock-to-first-attacker is not a profile setting.** It lives in RaidableBases' main config, in two
pairs of switches (PvP and PvE): one pair for "Manual" bases, which is what `rbevent` and the helper
spawn, and one pair for scheduled bases. A per-event lock would need a second Harmony patch, on the
base's `BypassUseOwners()`. Not built; a question for the org lead.
- **An admin-given spot works.** The helper builds the same spawn request the plugin's own "spawn where I
look" command builds, from numbers instead of a player's view. It then calls the plugin's paste. Bases
landed at the given x and z every time.
- **Height has to go through a copy of the profile.** The plugin recomputes y at paste time from the
ground (or water), plus the building's own height, plus the profile's paste height adjustment. It
ignores any y it is handed, unless the profile forces a height. So the helper copies the profile:
- an adjustment is added to the copy's paste height adjustment;
- an absolute y turns on the copy's forced height.
Results, measured as the height of the base's building blocks:
| Request | Blocks at | Ground there |
| --- | --- | --- |
| no adjustment | 1–4 | −0.2 |
| +5 | 6–9 | −1.1 |
| absolute y = 30 | 31–34 | 0.5 |
| a plain `rbevent` spawned right after | 1–4 | −0.2 |
**Trap:** the plugin's own `BuildingOptions.Clone()` is a shallow `MemberwiseClone`. The first run changed
the height through that clone, and the change leaked into the shared profile: the next plain `rbevent`
base spawned 35 m up. The height settings sit in a nested object, so the helper must copy that too, or
use the same deep copy as the overrides. Nothing reached the file on disk, and `rb.reloadprofiles` reset
the leak.
- **The plugin's area check catches buildings, but not water.** Its safety check, run at an admin's spot,
refused a player's building and refused an existing raid base. At 25 m away from the building it passed. A
spot on the seabed 50 m under water also passed: the plugin tests water in its own location picker, not
in this check. So an admin-given spot needs the helper's own water and terrain test as well as the
plugin's check. The spike reported the check but did not enforce it; whether to enforce is a question
for the org lead.
- **A RaidableBases reload drops the helper's Harmony patches**, because the patched methods belong to the
old assembly. The helper must re-patch in `OnPluginLoaded`, as the zone helper does for ZoneManager.
- **After a server boot the plugin refuses every spawn for about 50 s**, until its log says "Grid
initialization completed". This includes the helper's spawns. The step has to wait for that, not fail
on it.
#### 11.4.5 Decided after the helper spike (2026-09-28)
The org lead answered §11.4.4's three questions the same day:
- **D206: lock-to-first-attacker is per event.** The helper adds a second Harmony patch, on each base's
`BypassUseOwners()`. For a base the helper tagged for a run, it answers from that run's setting. Every
other base, including one an admin spawns by hand, still follows RaidableBases' config. Without the
helper the lock follows the config, and the step says so.
- **D207: a step places its base one of two ways.** The default is D203, where RaidableBases picks. The
other is an admin-given spot: x and z, plus either a height adjustment (added to the profile's own) or
an exact height (the profile's forced height). Both go through the helper's deep copy of the profile
(§11.4.4). The spot needs the helper; without it the step offers only the plugin's pick.
- **D208: a blocked spot is refused, and the run log says why.** At spawn time the helper runs
RaidableBases' own area check and its own water and terrain test. If either fails, the step does not
spawn, and the run records the reason ("player building", "raid base", "in water"). The editor
runs the same check through the bridge when the step is saved. That is advice only, since the world
can change before the event runs.
### 11.5 Protocol
These are new verbs, events and hello fields. If they are built before the cutover they join the unreleased