13 Commits

Author SHA1 Message Date
7483e131ef Merge pull request 'fix(perms): never write a never-seen player into Carbon's shared blank record' (#47) from fix/carbon-blank-user into main
All checks were successful
Release plugin / release (push) Successful in 43s
Reviewed-on: #47
2026-10-30 09:31:54 +00:00
59db88d006 fix(perms): never write a never-seen player into Carbon's shared blank record
Some checks failed
PR Checks / plugin-checks (pull_request) Failing after -14s
Carbon 2.0.262 answers GetUserData for a Steam id it has no record for with
one shared, static blank record, and GrantUserPermission and AddUserGroup
write into it. A grant or membership for one never-seen player was then held
by every Steam id Carbon had no record for, until the server restarted, and
UserHasGroup read the membership back as a success, so the sync reported it
applied and later syncs saw nothing to fix.

On Carbon the sync now checks permission.UserExists before either write. A
never-seen player's grant is reported notLanded and their membership pending,
without touching the store; both land at the first audit sync after they
join, as PLAN.md section 29.8 and section 12.2 rule 4 already describe. Oxide
creates a real record and is unchanged.

Proven on the Carbon rig (Rust 26139947, Carbon 2.0.262): before, three
random never-seen ids each held autodoors.use after one admin grant; after,
a known id got its grant and membership, the never-seen id was reported
notLanded + pending, and random ids held nothing. Found in the account
deletion walk (docs/website/ACCOUNT_DELETION.md step 6, W3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-30 04:23:31 -05:00
19ad9279fa Merge pull request 'docs(bridge): bring EventsEnabled's comment to main' (#46) from edge into main
All checks were successful
Release plugin / release (push) Successful in 15s
Reviewed-on: #46
2026-10-23 07:07:09 +00:00
22da0026ca Merge pull request 'docs(bridge): EventsEnabled's comment says what it gates today' (#45) from fix/eventsenabled-comment into edge
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 22s
Reviewed-on: #45
2026-10-22 20:32:36 +00:00
ef9596ce67 docs(bridge): EventsEnabled's comment says what it gates today
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 9s
The doc comment on PluginConfig.EventsEnabled still said it "gates
taking a lease only", which stopped being true at protocol 9 when it
began gating every world write (D94). It now lists what the switch
gates as of protocol 14 - a lease, every world write (zones, crates,
NPCs, world calls, Dangerous Treasures, Guarded Crate, raid bases,
console commands), a tally and a run's chat line, and paying coins -
what always works with it off, and that the website can set it without
a reload. Comment only; no behaviour change.

Found while writing the runicgateway.com Rust docs (its PLAN.md §16,
phase 15).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-22 15:29:34 -05:00
25a698e86e Merge pull request 'chore(release): cut edge over to main — protocol 14' (#44) from edge into main
All checks were successful
Release plugin / release (push) Successful in 30s
Reviewed-on: #44
2026-10-22 10:22:46 +00:00
3c0fcb0003 Merge pull request 'fix(bridge): the last fixes' bridge half (W3, W9, W12)' (#43) from fix/last-fixes-bridge into edge
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 13s
Reviewed-on: #43
2026-10-22 00:35:42 +00:00
38f854d580 fix(bridge): the last fixes' bridge half (W3, W9, W12)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 14s
Stage 3 of docs/modules/rust/PLAN_LAST_FIXES.md.

- W3: a Travelling Vendor call while one is on the map answers world.error
  reason `already-out` instead of "called 1" with nothing spawned. Not
  counted against the run's budget and not kept under its key, so a retry
  looks again. Module-Rust#55 reads it as called 0.
- W9: a rolled-back RaidableBases profile write carries RaidableBases' own
  log entry for the file (its path and the exception's message, without the
  stack), read from the log since the write began, instead of the last 8 KB
  of the server log. Its other entries since the write stand in when none
  names the profile, and nothing when it said nothing.
- W12: raid.ended carries the base's id, and its runId and key, when a run
  made it, as PROTOCOL §20.12 says. A teardown takes the base off the record
  before its despawn raises Ended, so the live base keeps its run and key too.

No protocol change: W3's reason is a new value of an existing field, and W12
sends the fields PROTOCOL already lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-21 13:59:10 -05:00
48aa62a07a Merge pull request 'feat(bridge): RaidableBases — raid.* events, raid.spawn, the raids helper (PLAN_REDESIGNS §11.4)' (#42) from feat/raidable-bases into edge
Reviewed-on: #42
2026-10-17 06:32:33 +00:00
e0d6c46850 feat(bridge): RaidableBases — raid.* events, raid.spawn, the raids helper (PLAN_REDESIGNS §11.4)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 11s
PLAN_REDESIGNS §11.4 (D202-D208, D386-D390), protocol 14. Spec: docs
rust-link/PROTOCOL.md §20.12.

- RunicGatewayRaids.cs, the raids helper (D168, D204-D207, D387), shipped
  beside the bridge and released with it. Harmony patches on RaidableBases'
  spawn queue Add, OpenEvent and each base's BypassUseOwners(): a run's base
  is tagged at birth, its PvP/NPCs/despawn/lock set on a member-by-member copy
  of its profile (Setup, NPC and DespawnOptions copied again, so the shared
  profile is never touched), PvP also set on the base after OpenEvent; a spot
  checked (own look, then the plugin's area check with its reason, rocks and
  cliffs a warning; then the ocean level, D388, D389) and pasted; one base
  despawned; profiles reloaded under watch. Both editions, every member found
  by name. rgr.status.
- Hello's integrations.raidableBases: edition, the paid spawn call (looked
  for by name, D348), CopyPaste, the plugin's own difficulties (D386), the
  helper, and with it the grid and the profiles.
- raid.started / raid.completed / raid.ended / raid.purchased / raid.locked
  from the lifecycle hooks; the unload trap's completions (no owner, no
  raiders) are neither sent nor counted. The tally's raidsCompleted (Warlord).
- map.live's raids layer, kept by the hooks and seeded from GetAllEvents().
- raid.spawn: with the helper the base is a world registry entry, so
  world.revert / world.owned / world.expired serve it (never-started after
  15 min, raidablebases-unloaded); without it the paid call or rbevent, not
  the run's. raid.check for the editor's advice (D208, D391).
- RaidableBases' profiles in the configuration editor (D205): a virtual
  @data/RaidableBases/Profiles/ path, all-profiles-or-none writes, reloaded by
  the helper and rolled back when a profile does not load.

Compiled on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262) and
driven on both rigs with the paid 3.2.2 and the tier-3 pack through a
throwaway plugin: the pick and spot routes with every setting, a repeat key,
revert, expiry, and a good and a broken profile edit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-16 19:29:02 -05:00
594acb5351 Merge pull request 'feat(bridge): console commands as event steps — command.run (PLAN_REDESIGNS §16, D382-D385)' (#41) from feat/command-steps into edge
Reviewed-on: #41
2026-10-16 07:47:40 +00:00
5104e0dbcd feat(bridge): keep a step's teardown with its key — teardownOf (D385)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 9s
Core records nothing for a step that failed or whose answer was lost, and
then reverts it with only the key: no server, no teardown. So a run request
now carries its `teardown`, stored in the key's record and never run with
it, and `{ key, teardownOf }` runs the teardown stored under `teardownOf`,
under its own key, at most once:

- only when something there may have run (some commands ran, or a reload
  cut the run off); otherwise answered with nothing run and nothing kept;
- `busy` while the run it is for is still going;
- a reward carries no teardown (refused `malformed`), and a teardown takes
  no placeholders.

Re-compiled and driven on both rigs (a failed run's teardown ran once, a
repeat ran nothing, nothing-ran and unknown keys ran nothing); rigs
restored to the protocol-13 bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-16 02:36:49 -05:00
b52b8a8f80 feat(bridge): console commands as event steps — command.run (PLAN_REDESIGNS §16)
A `command.run` frame (protocol 14, PROTOCOL.md §20.11) runs the console
commands a step's author typed: once, or once per online recipient with
{steamid} and {name} filled in (the name quoted the way the game's own
BuildCommand quotes an argument). D382-D384: no allowlist; offline
recipients are skipped and named.

- At most once per key: the key is written to commands-run.json before
  anything runs; a repeat answers from the record, a repeat while running
  joins the running job, a record a reload interrupted answers
  `interrupted`. Kept 7 days, at most 500 records.
- Spread over frames: whole recipients per frame, up to 30 commands or 8 ms.
- unknown-command: ConsoleSystem.Run returns null both for an unknown command
  and for one that replies nothing, so the run listens to Unity's log for the
  game's own "Command '...' not found" line while each command runs (spiked
  on Oxide 2.0.7830 and Carbon 2.0.262, Rust 25902425). The same capture is
  the reply when Run returns nothing.
- rg.world shows commandKeys and commandsRunning.

Compiled on both rigs and driven through a throwaway plugin (repeat key,
unknown command, offline recipients, malformed requests); rigs restored to
the protocol-13 bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-16 02:30:59 -05:00
4 changed files with 4260 additions and 19 deletions

View File

@@ -30,7 +30,7 @@
#
# 2. A FRAMEWORK-NEUTRAL LAYOUT. The tarball holds one directory,
# `runicgateway-rust-plugin/`, with `RunicGateway.cs`, its helpers
# (`RunicGatewayZones.cs`, `RunicGatewayDomes.cs`) and `manifest.json` at its top. Every `.cs` in it is listed in the
# (`RunicGatewayZones.cs`, `RunicGatewayDomes.cs`, `RunicGatewayRaids.cs`) and `manifest.json` at its top. Every `.cs` in it is listed in the
# manifest's `files`, and the installer and the egg place every one of them. It does NOT mirror `oxide/plugins/`: the same file goes to
# `oxide/plugins/` or `carbon/plugins/`, and the installer and the egg each
# decide which (§34.2.1). The fixed, unversioned prefix is so a reader never
@@ -41,7 +41,8 @@
# committed file keeps its placeholder; the manifest records the commit.
# Only the bridge is stamped. A helper (docs/modules/rust/PLAN_FIXES.md D168,
# D182 — today `RunicGatewayZones.cs` for ZoneManager and `RunicGatewayDomes.cs`
# for ZoneDomes, PLAN_REDESIGNS D194) keeps its own
# for ZoneDomes, PLAN_REDESIGNS D194, and `RunicGatewayRaids.cs` for RaidableBases,
# D204) keeps its own
# version: it does one job, changes rarely, and the bridge reads its version at
# hello to know which job it does.
#
@@ -81,7 +82,7 @@ env:
PLUGIN: overlay/oxide/plugins/RunicGateway.cs
# The helpers shipped beside the bridge (D168, D182). Optional at runtime —
# the bridge loads without them — and installed by default.
HELPERS: overlay/oxide/plugins/RunicGatewayZones.cs overlay/oxide/plugins/RunicGatewayDomes.cs
HELPERS: overlay/oxide/plugins/RunicGatewayZones.cs overlay/oxide/plugins/RunicGatewayDomes.cs overlay/oxide/plugins/RunicGatewayRaids.cs
SEED_VERSION: "0.1.0"
INSTALLER_REPO: RunicGateway/installer

View File

@@ -37,6 +37,7 @@ unauthenticated command channel on the network.
overlay/oxide/plugins/RunicGateway.cs → <server>/oxide/plugins/RunicGateway.cs
overlay/oxide/plugins/RunicGatewayZones.cs → <server>/oxide/plugins/RunicGatewayZones.cs
overlay/oxide/plugins/RunicGatewayDomes.cs → <server>/oxide/plugins/RunicGatewayDomes.cs
overlay/oxide/plugins/RunicGatewayRaids.cs → <server>/oxide/plugins/RunicGatewayRaids.cs
```
`RunicGatewayZones.cs` is the **ZoneManager helper** (docs/modules/rust/PLAN_FIXES.md D181, D182). ZoneManager
@@ -52,6 +53,17 @@ helper patches the first with Harmony and tells the bridge when ZoneDomes has st
domes back without drawing any twice. It is optional: without it the site offers no dome, and says which piece is
missing. `rgd.status` in the server console says whether it patched.
`RunicGatewayRaids.cs` is the **RaidableBases helper** (docs/modules/rust/PLAN_REDESIGNS.md §11.4, D204–D207,
D387). RaidableBases' API spawns nothing and despawns nothing, so an event's base cannot be the event's from the
outside. The helper reaches its internals by reflection and patches its spawn queue, `OpenEvent` and each base's
`BypassUseOwners()` with Harmony: it tags the base a run asked for at birth, applies that spawn's own PvP, NPCs,
despawn time and lock on a copy of its profile, places one at a spot after checking it, despawns that one base at
the run's end, and reloads RaidableBases' profiles when the site edits them. It serves both editions, free and
paid, and reads every difficulty, profile and base name from the installed plugin. It is optional: without it the
bridge still reports raid bases on the feed and the map, and a step can still ask RaidableBases for a base where it
picks, but that base despawns on its own timer, and the site says so. `rgr.status` in the server console says
whether it patched, which edition's spawn call it found, and whether the plugin's spawn grid is built.
Oxide compiles and loads it on the write, and writes `oxide/config/RunicGateway.json` on first load:
```json
@@ -134,10 +146,30 @@ kept when the event ends: Guarded Crate has no way to end one event alone. Witho
is refused, because the crate would land unguarded; and, as for Dangerous Treasures, a framework with
`consolePlugins=False` cannot place one.
nivex's `RaidableBases` is optional, free or paid, and needs `CopyPaste` to paste its bases. Where it is
loaded, a base appearing, being raided, bought or locked to a player reach the site's feed, its map's own
*Raid bases* layer and the *Warlord* title, and an event can spawn one (`raid.spawn`). With
`RunicGatewayRaids.cs` (above) the base is the event's: at a spot or where RaidableBases picks, with its own
settings, despawned when the event ends; and the site can edit RaidableBases' profiles from its configuration
page, reloading them under watch. For up to ten minutes after RaidableBases loads it builds its spawn grid and
refuses every spawn, and a spawn then is answered "not ready". Every difficulty, profile and base name is read
from the installed plugin.
nivex's `ZLevels Remastered` is optional, and the bridge only says whether it is loaded. Its levels are
never read or sent. Its XP boosts are its own permissions (`zlevelsremastered.vip1` to `vip3`), so an
event lends one as it lends any other permission, and it takes effect at once.
An event can also run console commands an admin typed (`command.run`): once, as a step, or once for each
recipient of a reward who is online, with `{steamid}` and `{name}` filled in (the name as one quoted
argument). That is how a plugin with no support of its own here is used from an event, for example
RaidableBases' `rbe`. There is no allowlist: the site's switchboard is the switch, and `EventsEnabled`
here is the server's. Each request runs at most once: its key is written to
`data/RunicGateway/commands-run.json` before anything runs, and is kept for seven days. A step's
teardown commands are kept with its key too, so the site can have them run even for a step that failed
part-way or whose answer was lost (`teardownOf`). A command the
server does not know stops the request there as `unknown-command`. On a framework with
`consolePlugins=False`, that is every plugin's command.
Rust's own world events need no plugin at all. The patrol helicopter, Bradley, the cargo ship, the
Chinook, airdrops, locked crates, the Travelling Vendor, the satellite and the Deep Sea reach the
site's feed and its event triggers on every server, and an event can call one of them (`world.call`).

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff