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
This commit is contained in:
2026-09-28 12:18:33 -05:00
parent 6684ea5c60
commit a34b5fdbf0

View File

@@ -823,10 +823,72 @@ The org lead answered the spike's three questions the same day:
guarantees: the write is recorded, a reload is watched, and a failed load rolls back (D178, D179). The 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. 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 **Still to settle in the build's own spike:** the same helper questions on Carbon and on the paid edition.
base, and which settings it can override after `Started`, on both editions. Also the one-at-a-time rule: the 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 base of the same name on a server while a first one is waiting to start, step refuses to spawn a second one, because the free edition's bases have no ids to tell them apart.
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.5 Protocol ### 11.5 Protocol