Commit Graph

78 Commits

Author SHA1 Message Date
6be14c7270 Merge pull request 'feat(bridge): Backpacks on protocol 14 — read a backpack (PLAN_REDESIGNS §11.3)' (#33) from feat/rust-backpacks into edge
Reviewed-on: #33
2026-10-12 19:41:33 +00:00
e3dc918400 feat(bridge): Backpacks on protocol 14 — read a backpack (PLAN_REDESIGNS §11.3)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 23s
WhiteThunder's Backpacks as an optional integration (D199), as §11.3.2 designs it.
Read, never write (R2):

- hello/status `integrations.backpacks` adds `sizes`: the N of every registered
  backpacks.size.N, ascending (dynamic-size profiles left out). Hello is resent
  when Backpacks loads or unloads.
- `backpacks.read {steamId}`: API_ReadBackpackContentsAsJson, and only when that
  answered a backpack, API_GetBackpackCapacityById — the backpack is loaded by
  then, so the capacity follows a later grant (§11.3.1's trap). Each stack is
  reduced to slot, shortname, display (the game's English name, D340), amount,
  skin and name when set; a blueprint carries its target as `blueprint` (D341);
  contents one level deep; `overflow` past the capacity. `pageSize` is
  Backpacks' "Max size per page" from its config, cached by mtime.
- Refusals: backpacks-missing, bad-id, unreadable.

The bigger backpack an event lends is a backpacks.size.N permission pushed by
perm.sync (D337), so it needs no code here. Still protocol 14 (D332).

Compiled on both rigs (Rust changeset 167584; Oxide 2.0.7826, Carbon 2.0.262),
then restored to the bridge they ran before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-12 14:35:54 -05:00
3db35cdd02 Merge pull request 'feat(bridge): Economics on protocol 14 — pay, balances, adjust (PLAN_REDESIGNS §11.2)' (#31) from feat/protocol-14-economics into edge
Reviewed-on: #31
2026-10-12 12:40:24 +00:00
66fbbd6b57 feat(bridge): Economics on protocol 14 — pay, balances, adjust (PLAN_REDESIGNS §11.2)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 10s
Wulf's Economics as an optional integration (D199), as §11.2.2 designs it:

- hello/status `integrations.economics` adds startingBalance and wipesWithMap,
  read from Economics.json (cached by mtime) because its API has neither.
- `economics.pay`: coins from an event step, kept once paid (D335). One main-
  thread call for the list; each deposit measured as after - before, since
  Deposit answers true while rounding and clamping (`at-limit` otherwise).
  The key and its reply are kept in data/RunicGateway/payments.json for seven
  days, so a retry answers `repeat: true` and pays nobody. The optional
  message ({amount} = what landed) is told now if awake, else on wake.
- `economics.balances` (bounded by PermMaxOps) and staff `economics.adjust`
  (deposit / withdraw / set; a withdrawal above the balance is refused
  `insufficient` with the balance).
- `player.tally` gains `balance`, a value: read at connect (D336) and from
  OnEconomicsBalanceUpdated, offline players included.
- `economics.wiped` from OnEconomicsDataWiped(IPlayer), with `newSave`.

Amounts are boxed as double and only Steam ids are sent, per the spike.
Compiled on both rigs (Oxide 2.0.7826, Carbon 2.0.262), then restored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-12 06:56:46 -05:00
cc7b2b2a0e Merge pull request 'feat(bridge): protocol 14 — kits.uses and reward notices (PLAN_REDESIGNS §2)' (#30) from feat/protocol-14-step-editor into edge
Reviewed-on: #30
2026-10-12 09:52:10 +00:00
6952fbf123 feat(bridge): protocol 14 — kits.uses and reward notices (PLAN_REDESIGNS §2)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 9s
Opens protocol 14 (D332):
- kits.uses {kit, steamIds} -> {kit, max, cooldown, asked, exhausted,
  onCooldown}: counts only, bounded by the sync's own bound (§2.6).
- perm.sync notices [{key, steamId, message, delivery}] (D333): the site's
  whole untold set. Told at once to a player who is awake, else on
  OnPlayerSleepEnded; popup where PopupNotifications is loaded and chosen,
  chat otherwise. Told keys persist in notices.json until the site stops
  sending them; the report lists noticesTold.

ProtocolVersion and overlay.toml 13 -> 14; OnPlayerSleepEnded joins
ExpectedHooks (checkPlugin: 55 hooks, all void).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-12 02:15:23 -05:00
f0f2e31345 Merge pull request 'feat(bridge): protocol 13 with RunicNPC, edge → main (runicnpc 9e step 3, merge 2 of 3)' (#29) from edge into main
All checks were successful
Release plugin / release (push) Successful in 24s
Reviewed-on: #29
v0.2.0
2026-10-09 07:20:24 +00:00
d391c521c7 Merge pull request 'fix: animals and heals count toward titles; an event's zone enforces its flags (runicnpc 9d, D320–D322)' (#28) from fix/walk-titles-zones into edge
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 13s
Reviewed-on: #28
2026-10-07 18:47:14 +00:00
a785ad91ef fix(zones): an event's zone enforces its flags (RunicGatewayZones 0.2.0, runicnpc 9d)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 13s
ZoneManager subscribes the hooks that enforce its flags only for the zones it
had when it loaded; a zone made later through its API, which is every zone an
event opens, never re-subscribes them, so an event's PvpGod or NoBuild zone
enforced nothing. The helper's postfix, which already sees every zone created,
updated or loaded, now has ZoneManager run its own UpdateHookSubscriptions on
the next tick, before entering the players already inside. A ZoneManager
without that method is warned about and reported by rgz.status; the first job
still works.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-07 13:41:57 -05:00
e5f04e0c0c fix(bridge): Rust's animals and heals count toward titles again (runicnpc 9d)
- Animal kills: Rust's animals are now Rust.Ai.Gen2.BaseNPC2 (stag, boar, wolf,
  bears, big cats, crocodile, farm animals, critters, sea life), not BaseNpc, so
  no animal kill counted as an animal or an NPC kill. They count as both again;
  ScientistNPC2, the one person among them, counts as a human NPC kill.
- Heals: OnHealingItemUse is (tool, fromPlayer, toTarget). The bridge took two
  parameters, received the healer as the target, and dropped every heal as
  healing yourself. It now reads the target, and one act of help (a syringe
  revive raises both hooks, the heal first) counts once whichever hook comes
  first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-07 13:41:57 -05:00
912b115e83 Merge pull request 'feat(bridge): every NPC an event places needs RunicNPC (runicnpc stage 9, D310)' (#27) from feat/runicnpc-stage9 into edge
Reviewed-on: #27
2026-10-07 02:43:10 +00:00
3117b0c349 feat(bridge): every NPC an event places needs RunicNPC (runicnpc stage 9, D310)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 6s
world.place of one of Rust's own NPC prefabs now needs RunicNPC loaded and
answering RunicNpcApiNeeded, as a profile placement does, so a module that
missed the hello cannot place NPCs on a server the site calls incomplete. The
refusal comes before anything spawns, with the existing reasons
runicnpc-missing and runicnpc-old; a profile placement now also refuses an old
RunicNPC up front (it was refused only when it carried orders). Crates are
unaffected. No message changes shape; protocol stays 13.

overlay.toml's runicnpc_api comment and rg.npc's line say RunicNPC is required.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-06 16:58:30 -05:00
87774b051a Merge pull request 'feat(bridge): map.live names RunicNPC's NPCs, marks bosses and adds (runicnpc stage 8)' (#26) from feat/runicnpc-stage8 into edge
Reviewed-on: #26
2026-10-06 13:32:31 +00:00
74bcb0f895 feat(bridge): map.live names RunicNPC's NPCs, marks bosses and adds (runicnpc stage 8)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 11s
One RunicNpc_List per map.live ask, joined to the ledger by net id
(D302, D303):

- A ledgered NPC's row gains its name and the step key that placed it.
  A boss's row also gains `boss` and its `health` (0-1); the health is
  for the event page's progress line.
- A RunicNPC NPC owned by a run that no step ledgered is a boss's add.
  It joins the events layer under that run with `add: true`.
- Any other boss joins the world layer as `kind: "boss"` with its name.
- A placement's ordinary NPCs are not sent.

Protocol 13, still unreleased; the fields are additive.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-06 08:23:10 -05:00
e966ea6c5b Merge pull request 'feat(bridge): a Place NPCs step's "drop loot" switch (runicnpc stage 7)' (#25) from feat/runicnpc-stage7 into edge
Reviewed-on: #25
2026-10-06 07:16:19 +00:00
1df3db57d3 feat(bridge): a Place NPCs step's "drop loot" switch (runicnpc stage 7)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 8s
world.place with a profile takes dropLoot (D296): false spawns the
NPCs with RunicNPC's {"loot":{"dropTable":false}} override, so they
keep their corpse's start but roll no table. Absent or true, as before.
runicnpc_api and RunicNpcApiNeeded are 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-05 22:36:29 -05:00
0792564ffe Merge pull request 'feat(bridge): boss frames for the site's public triggers (runicnpc stage 6)' (#24) from feat/runicnpc-stage6 into edge
Reviewed-on: #24
2026-10-06 00:45:32 +00:00
90f403422a feat(bridge): boss frames for the site's public triggers (runicnpc stage 6)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 8s
npc.boss.spawned, npc.boss.phase and npc.boss.died, from RunicNPC
API 5's boss hooks, for the site's spawned, phase and killed triggers
(D277). The death frame names the killer and counts the players who
hurt the boss; npc.died still carries every contributor.

A profile's `boss` and `use` blocks travel in the push unchanged.
runicnpc_api and RunicNpcApiNeeded are 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-05 10:48:28 -05:00
9a3ae47850 Merge pull request 'feat(bridge): the faction table and an event's escort, ally and tether (runicnpc stage 5)' (#23) from feat/runicnpc-stage5 into edge
Reviewed-on: #23
2026-10-05 08:38:55 +00:00
cf26b05abc feat(bridge): the faction table and an event's escort, ally and tether (runicnpc stage 5)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 19s
- npc.profiles answers with RunicNPC's faction table, and npc.profiles.set
  pushes one beside the profiles (RunicNpc_SetFactions), set first so the
  profiles are checked against it; a refused table comes back as
  factionsRefused and the old one is kept (D254);
- world.place with a profile takes escort (a Steam id; the player must be
  on, else escort-offline), ally ({kind: clan|player, id}) and tether (the
  run's own zone that holds the point, else no-zone), and gives each NPC
  its orders through RunicNpc_Escort, _Ally and _Tether. All or nothing:
  an order RunicNPC refuses removes every NPC the step placed (D269, D270,
  D272);
- the RunicNPC API the bridge needs is 4 (overlay.toml runicnpc_api = 4).

Compiled on the Oxide rig.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-01 07:58:16 -05:00
d1039ff95d Merge pull request 'feat(bridge): RunicNPC, the site's NPC profiles and placements (runicnpc stage 4)' (#22) from feat/runicnpc-stage4 into edge
Reviewed-on: #22
2026-09-30 19:52:41 +00:00
6347c25d57 feat(release): declare the RunicNPC API the bridge needs (runicnpc stage 4, D224)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m36s
overlay.toml gains runicnpc_api = 3, the release folds it into manifest.json,
and checkPlugin.js holds it equal to RunicNpcApiNeeded in the code. The
installer's bundle carries the latest RunicNPC release that answers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-30 04:48:51 -05:00
742e18ba0a feat(bridge): RunicNPC, the site's NPC profiles and placements (runicnpc stage 4)
Protocol 13 gains npc.profiles, npc.profiles.set, npc.placements and
npc.placement (add, set, remove, rename, respawn), each calling RunicNPC's
API 3; the frames npc.died, npc.health and npc.placement.changed from its
hooks; runicNpc in the integrations body; world.place with a profile; the
killfeed's attackerNpc and attackerProfile; and the tally's
npcProfileKills. rg.npc reports it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-30 04:21:50 -05:00
428055f6ef Merge pull request 'feat(bridge): count the twenty-three chat-title conditions in player.tally (PLAN_REDESIGNS §5)' (#20) from feat/title-conditions into edge
Reviewed-on: #20
2026-09-29 17:01:02 +00:00
f7d5b4d691 Merge edge into feat/title-conditions: §3's zones and domes
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 6s
One conflict, ExpectedHooks: both phases' hooks are kept (48, all void).
Compiled and loaded on both rigs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-29 11:34:26 -05:00
463a50dbb3 Merge pull request 'feat(bridge): a zone's ZoneManager options, its messages, and a ZoneDomes dome (PLAN_REDESIGNS §3)' (#21) from feat/zones-domes into edge
Reviewed-on: #21
2026-09-29 16:28:51 +00:00
b30c6edd59 feat(bridge): a dome without a type is the full dome (D212)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 7s
ZoneDomes' Standard type is its one whole dome; the four colours show only
where they meet the ground or a building. Walked on both rigs 2026-09-29.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-29 09:42:17 -05:00
746523387e feat(bridge): a zone's ZoneManager options, its messages, and a ZoneDomes dome (PLAN_REDESIGNS §3)
world.zone gains flags, settings, enterMessage, leaveMessage, delivery,
format and dome (PROTOCOL §19.11). Flags are checked against the live
ZoneManager's own list, read through its API call ZoneFieldListRaw() (D211),
and refused bad-option rather than dropped; settings are an allowlist of
five with bounds. Both are kept on the registry entry and re-applied when
a zone is re-created.

The bridge says a zone's enter and leave messages itself (D193), as chat
or a popup (chat when PopupNotifications is missing), and stays quiet for
5 s after re-creating a zone.

RunicGatewayDomes.cs, a new helper, patches ZoneDomes so its API can be
called without a player, and raises OnRgDomesReady after ZoneDomes' own
start; the bridge puts its domes back only after it, so none is drawn
twice. Domes are removed before their zone at expiry and revert.

hello carries zoneManager { version, flags }, integrations.zoneDomes the
ZoneDomes and helper state, and hello is sent again when ZoneManager,
ZoneDomes or either helper loads or unloads. The release ships the new
helper beside the zone helper.

Not compiled or walked on a rig: the panel's file API was down on both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-29 05:25:43 -05:00
f67f9e3b22 fix(bridge): the three members the rig corrected — crafter owner, mission assignee, heal target
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m28s
Compiled on both rigs (2026-09-29): ItemCraftTask has no owner, so the crafter is ItemCrafter.owner; OnMissionSucceeded credits its third argument, the assignee, since MissionInstance has no accessor for it; OnHealingItemUse takes its target as object, because GiveEffectsTo now takes an IMedicalToolTarget, and counts only a player. GameObject.ToBaseEntity is not in scope for a plugin, so OnEntityBuilt asks GetComponent<BuildingBlock>().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-29 02:07:29 -05:00
ff49f8f6ed feat(bridge): count the twenty-three chat-title conditions in player.tally (PLAN_REDESIGNS §5)
player.tally gains animalKills, weaponKills (by the killfeed's weapon
prefab name), headshots, bestPvpM/bestPveM (this interval's maximum),
apcKills, heliKills, crafted (by item category), built, repaired, healed,
rockets, explosives and missions. Every field is absent when nothing moved.

Ten new void hooks, all in ExpectedHooks: OnEntityTakeDamage (the last
player to hurt an APC or helicopter, for a crash that names nobody),
OnItemCraftFinished, OnEntityBuilt, OnStructureRepair (counted only when
the health rose by the next tick), OnHealingItemUse and OnPlayerRevive
(another player only; a syringe revive counts once), OnRocketLaunched,
OnExplosiveThrown/Dropped (a damaging TimedExplosive only) and
OnMissionSucceeded.

npcKills keeps counting animals too, so the public column does not change
meaning mid-wipe; the site takes human NPCs as the difference (D209).
WeaponOf and DistanceOf are split out of DescribeAttacker so the killfeed
and the tally read one name and one distance.

Not yet compiled on a rig: the rigs' storage is unmounted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-28 23:32:58 -05:00
3f44d78944 Merge pull request 'feat(bridge): read the whole permission store (perm.inventory), and sync group parents' (#19) from feat/perm-inventory into edge
Reviewed-on: #19
2026-09-28 16:38:56 +00:00
8b4b65249f feat(bridge): read the whole permission store (perm.inventory), and sync group parents
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 9s
The permission manager (PLAN_REDESIGNS section 1, D160): the site owns every
permission and group on the server, so the bridge now reads all of it.

- perm.inventory: every registered permission with the plugin that
  registered it (PermissionExists(name, plugin), public on both
  frameworks), every group with title, rank, parent and permissions,
  every holder's direct permissions and groups (not default), and the
  pairs event leases hold. Built a slice per tick, sent in pages under
  the 1 MiB line cap, paged from one snapshot. rg.inventory logs it.
- perm.sync sets an existing group's title and rank and every group's
  parent; an absent title or rank means "leave it" (the adopt policy's
  hold). The managed list and the foreign scan are gone: the site reads
  the store and decides.

Walked on both rigs: Oxide 85 permissions all owned, Carbon 104 with its
30 adminmodule.* unowned; ZoneManager owns ignoreflag.nokits.

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 06:58:39 -05:00
7a1f5aa072 Merge pull request 'fix(bridge): protocol 13 step 2 — expiry, gathering, raids, plugin loads, and a ZoneManager helper (F1 F3 F4 F8 F12 F13)' (#18) from fix/protocol-13-step2 into edge
Reviewed-on: #18
2026-09-27 06:11:46 +00:00
d1d4a00d1d fix(bridge): the ZoneManager helper copies the player list without ToArray
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 10s
Found compiling on the rust-oxide rig: BasePlayer.activePlayerList is
Facepunch's ListHashSet, which has no ToArray, so the helper did not compile.
It now copies into a List, which it needs anyway: entering a player can eject
or kill them and change the live list mid-loop. HarmonyLib itself resolved on
Oxide with no reference directive, and the helper patched ZoneManager 3.1.14 on
both frameworks once this compiled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 21:43:22 -05:00
586ad7ceba 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
2026-09-26 21:36:04 -05:00
fc7dccb90b Merge pull request 'fix(bridge): protocol 13 — a configuration reload that works on Carbon (W1-W5)' (#17) from fix/protocol-13-carbon-reload into edge
Reviewed-on: #17
2026-09-27 01:23:56 +00:00
616f4fbca8 fix(bridge): protocol 13 — a configuration reload that works on Carbon (W1-W5)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 9s
The step-1 rig walk (PLAYER_WALK configuration 2, 2b, 4 on both rigs) found:

- W3: Carbon's Oxide-compatible ReloadPlugin is not a reload. One call unloaded
  Kits and never compiled it again; the next did nothing. Carbon now reloads
  with `c.reload` on the server console (ConsoleSystem.Run), as an operator
  would at the panel. Oxide keeps ReloadPlugin, falling back to `oxide.reload`.
- W4: Carbon's log is carbon/logs/Carbon.Core.log, with no *.txt in the
  directory at all, so the log evidence never fired on Carbon. Chosen by name.
- W5: Carbon loads a plugin whose LoadConfig threw and raises Loaded in the
  same instant. OnPluginLoaded is now noted, and the job settles on the first
  log read a poll after it. Carbon's "Failed ILoadConfig" is on the word list.
- W2: Oxide's "Could not initialize plugin 'Kits v4.4.9' (JsonReaderException:
  ...)" is on the word list, so the outcome quotes the exception.
- W1: Oxide's idle compiler shutdown drops a queued reload with "Timed out
  waiting for plugin to be compiled: X". The reload is asked for once more;
  a second give-up settles at once by what is loaded.

rg.config prints the log file it follows (log=).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 20:21:06 -05:00
5de406f356 Merge pull request 'fix(bridge): protocol 13 opens — UTF-8 from the site, a reload that fails on evidence (F15, F9, F10)' (#16) from fix/protocol-13-config-reload into edge
Reviewed-on: #16
2026-09-26 22:22:09 +00:00
042727e5fe fix(bridge): protocol 13 opens — UTF-8 from the site, a reload that fails on evidence (F15, F9, F10)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 20s
F15: the read loop cast each byte to a char, decoding the site's UTF-8 as
Latin-1, so every non-ASCII announcement, title, kit name and saved config
file arrived mangled. Lines are now split as bytes and decoded whole, which
keeps a character that straddles two reads intact.

F9/F10 (D177, D178): config.write answers at once with pending, writeId and
ceilingMs, and the outcome follows as a config.outcome event. A pending
reload reads the framework log each second and rolls back the moment the
target fails to compile or initialise; a slow compile is waited for, under
a 30 s ceiling per phase. At the ceiling the plugin looks at what is loaded
before restoring anything, and a rollback waits for the restore's own
reload so the outcome says whether the plugin is back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 17:04:41 -05:00
897233c75f Merge pull request 'fix(bridge): dial the configured port, and back off when refused (D154, D155)' (#15) from fix/config-before-link into main
All checks were successful
Release plugin / release (push) Successful in 16s
Reviewed-on: #15
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
v0.1.1
2026-09-26 06:27:02 +00:00
3e928bb2f6 fix(bridge): a backoff that holds against a sidecar that refuses (D155)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m38s
The reconnect backoff waited on `Wake`, which `Emit` signals on every
frame, so it lasted only until the next hook fired. That was invisible
while a failed connect was silent; against a sidecar that now refuses a
plugin naming another server (D155) the phase 18 walk measured some 250
redials a second, each printing two console lines and rebuilding the
boards. It now waits on `_stopping`, the handle that already exists so a
CONNECT is not abandoned by `Emit`; Unload still ends the wait at once.

The backoff also resets only after a link has held for 10 s
(StableLinkMs). A connection closed on arrival used to reset it to 500 ms
on every accept, so a refusal loop never backed off at all.

Walked: a plugin aimed at another server's sidecar is refused every 5 s
(the ceiling), not continuously; a stable link whose sidecar restarts is
back in 0.49 s, as before; no Unload stall on reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 01:19:23 -05:00
04491e70af fix(bridge): read the config before the link thread dials (D154)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m29s
Init() started LinkLoop before Loaded() read the config, so the thread's
first dial saw _config == null and used the 7799 fallback on both Oxide and
Carbon. One server per host made that its own sidecar by coincidence. With
two on a host (the installer's named instances, phase 18), the second
server dialled the FIRST server's sidecar, sat in its accept backlog, and
the moment the first server's plugin reloaded that sidecar took the second
server's connection: the website showed server "alpha" with beta's
hostname and wipe.

LoadConfigValues() now runs in Init() before the thread starts; Loaded()
keeps its log line and the lease/world/reward loads.

Walked on the phase 18 rig: three instances (two Oxide, one Carbon 7801),
each now dials its own port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-26 01:07:41 -05:00
85f21c9c0d Merge pull request 'chore(release): cut edge over to main — protocols 3–12 and the first release (rust phase 18, D145)' (#14) from edge into main
All checks were successful
Release plugin / release (push) Successful in 15s
Reviewed-on: #14
Reviewed-by: Colby Whitlock <whitlocktech@gmail.com>
v0.1.0
2026-09-26 05:44:46 +00:00
a38be86e8d Merge pull request 'feat(bridge): server id from the environment, and the first release workflow (phase 18)' (#13) from feat/phase-18-release into edge
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 10s
Reviewed-on: #13
2026-09-26 05:34:23 +00:00
497b7c4f64 feat(bridge): server id from the environment, and the first release workflow (phase 18)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m35s
Module-rust phase 18, step 3 of docs/modules/rust/PLAN.md §34.2.7.

The environment seam (D150): when the plugin writes its default config and
RUSTLINK_SERVER_ID holds a valid id (the site's ^[a-z0-9][a-z0-9-]{0,63}$),
the file's ServerId is that value. It is read nowhere else: after that write
the file is canonical and the site's lock applies. An invalid value is logged
and ignored. This is how the egg names a server; not a message change, so the
protocol stays at 12.

The release (D145, §34.2.1): release.yml reuses servuo-plugins' release engine
unchanged (tag-only, secrets preflighted, orphan tags recovered and swept, the
installer's bundle.yml dispatched at the end). The adapter re-runs
checkPlugin.js and packages runicgateway-rust-plugin-<version>.tar.gz with a
framework-neutral layout: RunicGateway.cs, its [Info] version stamped to the
release, and a manifest.json folding in overlay.toml.

overlay.toml gains min_carbon_version = "2.0.259", the build R19 was proven on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 22:54:38 -05:00
78726b89de Merge pull request 'feat(bridge): protocol 12 — BetterChat titles and styles, popups (phase 17)' (#12) from feat/phase-17-integrations into edge
Reviewed-on: #12
2026-09-25 23:31:02 +00:00
5ff88610a5 fix(bridge): keep the chat titles across a reload (D144)
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in 22s
The walk found that a hot reload of this plugin emptied every title, and
the site never re-sent them: it re-sends on a new boot id or wipe id, and a
reload is neither, because the boot id names the server process. The set
is now written to titles.json with the wipe it was pushed on, and read at
OnServerInitialized only when that wipe is still the current one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 18:23:28 -05:00
8c6146259e feat(bridge): protocol 12 — BetterChat and popups (phase 17)
- titles.set: a whole set of chat titles, swapped in by reference and read
  by a BetterChat third-party title getter (one lookup, no socket). The
  getter is registered at load and again whenever BetterChat loads.
- perm.sync: a group may carry its BetterChat style. Each field is written
  through API_SetGroupField only when the game still holds what the site
  last pushed (or BetterChat's default); anything else is reported as
  drift. One `chat group set` per touched group saves it, because the API
  never does (D143). A retired style removes the group with `chat group
  remove` (D139). The report gains `chat`.
- chat.say: delivery chat|popup and an optional voice format. A popup with
  PopupNotifications absent is refused `popup-unavailable`. Every line said
  is logged, since the game logs neither a broadcast nor a popup.
- server.hello/status carry `integrations`; `rg.titles [steamId]` prints
  what BetterChat would render for a player.
- overlay.toml: protocol 12; PopupNotifications leaves requires_plugins
  (D141).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 17:48:05 -05:00
b5b13161c7 Merge pull request 'feat(bridge): protocol 11 — the map (phase 14)' (#11) from feat/phase-14-map into edge
Reviewed-on: #11
2026-09-25 12:00:22 +00:00
ef78da8ce6 fix(bridge): rg.map re-reads the picture rather than printing the last answer
All checks were successful
PR Checks / plugin-checks (pull_request) Successful in -1m35s
The walk emptied the Rust+ cache and rg.map still said `companion`: it
printed the picture the last map.info had seen. It now re-reads while the
world is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-09-25 06:31:42 -05:00