chore(release): cut edge over to main — protocol 14 #44
Reference in New Issue
Block a user
No description provided.
Delete Branch "edge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
Stage 6 of
docs/modules/rust/PLAN_LAST_FIXES.md: the protocol 14 release.edge→main.overlay.tomldeclaresprotocol = 14, which matches Rust-Link'sPROTOCOL_VERSIONin the companion PR (Rust-Link#32).Everything here was already reviewed into
edge: the kit weekend and reward notices (PLAN_REDESIGNS §2), Economics(§11.2), Backpacks (§11.3), Rust's own world events (§14.3.1), TruePVE in a zone (§14.3.2), Dynamic PVP (§14.3.3),
Dangerous Treasures (§14.3.4), Guarded Crate (§14.3.5), ZLevels in hello (§14.3.6), console-command steps (§16, D385),
RaidableBases (§11.4), and the last fixes' bridge half (W3 W9 W12, #43).
Merge with Rust-Link#32 and installer#42. Until both bridge halves are released, the bundle job's Gate 1 refuses
the pair. That is expected, and it composes once both are out. Module-Rust's cutover comes after the bundle.
How it was tested
Each feature's PR passed this repo's checks and was walked on both rigs (Oxide + Carbon) in the end walk and the last
fixes' stage 3 (PLAN_LAST_FIXES §8). After release I check
SHA256SUMSand the manifest (protocol 14,runicnpc_api6), then install the released bundle on both rigs with the released installer.Checklist
AI-assisted contributions (required)
Claude Code (Claude Opus 5.5). I have reviewed and understand every change, andtake responsibility for it. AI-authored commits are marked with a
Co-Authored-Bytrailer.License
the right to contribute it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
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_01E14m6SuuY6i1vASFeGDBeYWulf'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_01E14m6SuuY6i1vASFeGDBeYWhiteThunder'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_01E14m6SuuY6i1vASFeGDBeYStage 3 of the §14.3 integration plan (D362, D368): world.zone's optional truePve ({exclude: true} or {ruleset}), all or nothing, unmapped on every path that erases one of our zones, with truepve.json for erases while TruePVE is away. Docs and PRs still to come. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeYStage 3 of the §14.3 integration plan (D362, D368), specified in docs rust-link/PROTOCOL.md §20.6. - world.zone's optional truePve: {"exclude": true} or {"ruleset": "name"}, exactly one, the name 1-64 characters. Malformed is `malformed`; TruePVE not loaded is `truepve-missing`. - All or nothing: the zone (and its dome) is made, then AddOrUpdateMapping. A false erases both and answers `truepve-ruleset` with the name (or `refused` for exclude). - The registry entry remembers its mapping, and every path that erases one of our zones calls RemoveMapping first: the deadline, world.revert, a zone world.owned finds gone, a new wipe. While TruePVE is away the id waits in data/RunicGateway/truepve.json, flushed at OnPluginLoaded(TruePVE) and at the bridge's own load. - hello's integrations.truePve { loaded, version }, sent again when TruePVE loads or unloads. rg.world prints truePve= and truePveLeftover=. Compiled and loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262, TruePVE 2.4.6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeYStage 4 of the §14.3 integration plan, specified in docs rust-link/PROTOCOL.md §20.7 and §17.3. - pvp.zone.created { zoneId, event, x, z, radius, shape, durationSec } from OnCreatedDynamicPVP, and pvp.zone.deleted { zoneId, event } from OnDeletedDynamicPVP. Neither is sent while the world is not live. - Size and place come from ZoneManager: a sphere's Radius, or half a cube's longer side (cargo's zone is a cube); the position is the zone's transform, so cargo's moves with the ship. - map.live gains a `pvp` layer of the live zones, kept by the two hooks and seeded from Dynamic PVP's own AllDynamicPVPZones / GetEventName at world ready and when it loads; a zone ZoneManager no longer holds is dropped. - hello's integrations.dynamicPvp, sent again when it loads or unloads; rg.world prints dynamicPvp= and pvpZones=; both hooks join rg.hooks. - The bridge never makes, maps or erases one of Dynamic PVP's zones. Compiled and loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeYStage 5 of the §14.3 integration plan (docs rust-link/PROTOCOL.md §20.8). - Three events from DT's own hooks: dt.started { x, z, key? }, dt.won { steamId, name, invaders (max 32), invaderCount, x, z } and dt.ended { x, z }. None while the world is not live; a win still counts. - The start hook fires inside the call that opens the chest, so the key a dt.start is running joins its start exactly, not by a ten-second window. - dt.start { key, level 0-2 } runs `dtevent <level>` through ConsoleSystem.Run and answers dt.opened { key, level, repeat, x, z }. A key already answered is answered again and opens nothing (kept seven days in dt-starts.json). Refusals: malformed, bad-level, dt-missing, console-unavailable (D363), dt-not-ready (nothing opened, DT's own "Manual Event Failed" reply, under fifteen minutes since DT loaded, D365), dt-no-spot (after), refused (anything else DT said). - The tally gains treasuresWon (Treasure Hunter, D375); map.live's world gains kind "treasure" for each running chest, seeded at load from DT's chests in the world (DT has no API that lists them). - Hello's integrations gain dangerousTreasures, sent again on DT's load and unload. rg.world prints dangerousTreasures, treasures, dtStarts and dtWarmingUp. README lists Dangerous Treasures. checkPlugin: 81 hooks, every one void and listed; 18 tests. Compiled and loaded on both rigs (Rust 25902425, Oxide 2.0.7830, Carbon 2.0.262); rg.world read dangerousTreasures=False treasures=0, then both went back on the protocol-13 bridge. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeYStage 6 of the §14.3 integration plan. Spec: docs rust-link/PROTOCOL.md §20.9. - gc.started, gc.ended (the clearer, or nobody) and gc.failed from Guarded Crate's three hooks; the start's key joins exactly, inside the call. - gc.place { key, event, location as a world call } runs `<command> position <event> x,y,z`; refuses npcspawn-missing itself, since Guarded Crate's `position` does not check and the crate would land unguarded (D364). - The running set is checked against Guarded Crate's private list (by reflection): the hotload seed, `stop all` (no hook), and which event ended. - Hello's integrations.guardedCrate with npcSpawn and the events from its data file; the tally's guardedWon (Warden); map.live's guarded rows; rg.world. - Guarded Crate's cargo plane no longer says airdrop.called. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeYStage 7 of the §14.3 integration plan (D374): hello's integrations gains zLevels { loaded, version }, sent again when ZLevels loads or unloads, and rg.world prints zLevels=. No level is read or sent; its XP boosts are permissions a run already lends. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeYA `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_01E14m6SuuY6i1vASFeGDBeYCore 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