fix(bridge): protocol 13 step 2 — expiry, gathering, raids, plugin loads, and a ZoneManager helper (F1 F3 F4 F8 F12 F13)

The bridge's half of PLAN_FIXES §6 step 2 (decisions D181-D185, docs#288).

- F13: `world.expired` carries the entity's kind as `what`. It used to write
  it over the frame's own `kind`, so every expiry left filed as "zone".
- F1 (D159): `gathered` also counts OnDispenserBonus (the final-hit bonus),
  OnCollectiblePickup (hemp, mushrooms, stones off the ground) and
  OnGrowableGathered (farmed plants); all three are void and in ExpectedHooks.
- F3: taking down a building block you own, or one on a cupboard you are
  authorised on, no longer counts toward `structures`. The frame still goes.
- F4: every remaining UserIDString is SteamIdOf(userID), so a player another
  plugin spawned is never `steamId: null`.
- F8 (D184): `plugin.loaded` / `plugin.unloaded`, once the world is ready,
  with the permissions that plugin added or removed (a diff of the registered
  list, taken a tick after the hook).
- F12 (D181, D182): RunicGatewayZones.cs, a D168 helper shipped beside the
  bridge, Harmony-postfixes ZoneManager's Zone.InitializeZone and, a tick
  later, enters every connected player ZoneManager's own IsPositionInZone
  places inside, through ZoneManager's own OnPlayerEnterZone. Without it (or
  when it cannot find what it patches) the bridge scores its zones by
  IsPositionInZone. Hello reports `zoneHelper: { state, version?, reason? }`.
- release.yml stages every helper beside the bridge and lists each .cs with
  its sha256 in manifest.files; the installer and the egg place them all.

Not yet compiled: this is walked on both rigs before it merges.

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-26 21:36:04 -05:00
parent fc7dccb90b
commit 586ad7ceba
5 changed files with 644 additions and 29 deletions

View File

@@ -29,8 +29,9 @@
# exact commit being released.
#
# 2. A FRAMEWORK-NEUTRAL LAYOUT. The tarball holds one directory,
# `runicgateway-rust-plugin/`, with `RunicGateway.cs` and `manifest.json` at
# its top. It does NOT mirror `oxide/plugins/`: the same file goes to
# `runicgateway-rust-plugin/`, with `RunicGateway.cs`, `RunicGatewayZones.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
# has to parse the version out of a path to find the manifest that states it.
@@ -38,6 +39,10 @@
# 3. THE VERSION IS STAMPED into the shipped copy's `[Info(…)]` attribute, so
# `oxide.plugins` / `c.plugins` on a server names the release it runs. The
# 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`, the ZoneManager helper) keeps its own
# version: it does one job, changes rarely, and the bridge reads its version at
# hello to know which job it does.
#
# The version is never the protocol. The protocol lives in overlay.toml and
# moves only when a message shape does; the version moves on every releasable
@@ -73,6 +78,9 @@ env:
REPO: RunicGateway/Rust-Plugins
ARTIFACT: runicgateway-rust-plugin
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
SEED_VERSION: "0.1.0"
INSTALLER_REPO: RunicGateway/installer
@@ -279,6 +287,9 @@ jobs:
run: |
set -euo pipefail
[ -f "$PLUGIN" ] || { echo "::error::${PLUGIN} is missing"; exit 1; }
for helper in $HELPERS; do
[ -f "$helper" ] || { echo "::error::${helper} is missing"; exit 1; }
done
[ -f overlay.toml ] || { echo "::error::overlay.toml is missing (protocol + framework declarations)"; exit 1; }
node scripts/checkPlugin.js
# The stamp below rewrites exactly one [Info(...)] line; zero or two
@@ -304,6 +315,19 @@ jobs:
grep -qF "[Info(\"Runic Gateway\", \"RunicGateway\", \"${VERSION}\")]" "${STAGE}/RunicGateway.cs" \
|| { echo "::error::stamping the version into [Info(...)] did not take"; exit 1; }
# The helpers go in as committed, each keeping its own [Info] version.
for helper in $HELPERS; do
cp "$helper" "${STAGE}/$(basename "$helper")"
done
# `files` names every .cs staged, with its sha256 — the installer places
# exactly this set, and checks each file against it.
PAIRS=()
for f in "${STAGE}"/*.cs; do
PAIRS+=("$(basename "$f")" "$(sha256sum "$f" | cut -d' ' -f1)")
done
FILES="$(jq -n '[$ARGS.positional | _nwise(2) | {(.[0]): .[1]}] | add' --args "${PAIRS[@]}")"
# Declarations from overlay.toml. Read, don't hardcode — the point of
# that file is that the protocol number lives in one place.
toml_str() { grep -m1 -E "^$1[[:space:]]*=" overlay.toml | sed -E 's/.*"([^"]+)".*/\1/'; }
@@ -329,7 +353,7 @@ jobs:
--arg min_oxide "${MIN_OXIDE}" \
--arg min_carbon "${MIN_CARBON}" \
--argjson requires "${REQUIRES}" \
--arg sha "$(sha256sum "${STAGE}/RunicGateway.cs" | cut -d' ' -f1)" \
--argjson files "${FILES}" \
'{
component: $component,
version: $version,
@@ -339,7 +363,7 @@ jobs:
min_oxide_version: $min_oxide,
min_carbon_version: $min_carbon,
requires_plugins: $requires,
files: { "RunicGateway.cs": $sha }
files: $files
}' > "${STAGE}/manifest.json"
echo "----- manifest.json -----"
cat "${STAGE}/manifest.json"