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
- 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
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
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
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
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
- 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
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
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
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
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
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
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
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
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
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
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