diff --git a/modules/rust/PLAN_REDESIGNS.md b/modules/rust/PLAN_REDESIGNS.md index 592de6e..11bb4c8 100644 --- a/modules/rust/PLAN_REDESIGNS.md +++ b/modules/rust/PLAN_REDESIGNS.md @@ -618,7 +618,7 @@ sources (D159), so the site sums named lists. | Condition (title) | Counted from | Tally field (new unless noted) | Site | |---|---|---|---| -| Animal kills (Beastbane) | `OnEntityDeath`, victim `BaseNpc` | `animalKills` | column | +| Animal kills (Beastbane) | `OnEntityDeath`, victim `BaseNpc` or `Rust.Ai.Gen2.BaseNPC2` (not `ScientistNPC2`; RunicNPC D320) | `animalKills` | column | | NPC kills (Exterminator) | `OnEntityDeath`, victim a human NPC | `npcKills` (exists, and keeps meaning *all* NPC kills) | `npc_kills − animal_kills` | | Players killed (Reaper) | `player.death` (exists) | — | `kills` | | Bow / Melee / Blade / Revolver kills (Fletcher, Brawler, Duelist, Outlaw) | every credited kill's weapon shortname, same source as the killfeed | `weaponKills: { shortname: n }` | new table, classified by lists (§5.3) | @@ -740,6 +740,13 @@ plan had assumed, each of which would not have compiled or would never have been - `MedicalTool.GiveEffectsTo` now takes an `IMedicalToolTarget`, so `OnHealingItemUse` takes its target as `object` and counts only a player. +**Corrected by the RunicNPC 9d walk (2026-10-07, D320–D322).** Two of the plugin's choices above were +wrong on a live server, and a third gap was ZoneManager's: Rust's animals are `Rust.Ai.Gen2.BaseNPC2`, not +`BaseNpc`, so no animal kill had counted (D320); `OnHealingItemUse` is `(tool, fromPlayer, toTarget)`, so the +two-parameter handler read the healer as the target and no heal counted (D321); and an event's zone did not +enforce its flags until RunicGatewayZones 0.2.0 has ZoneManager refresh its hooks (D322, §3). See +[`../../runicnpc/PLAN.md`](../../runicnpc/PLAN.md) stage 9d. + **Compiled on both rigs, 2026-09-29.** `rust-oxide` (a fresh install on the published egg) and `rust-carbon`: all 45 hooks bound, and `OnEntityTakeDamage` fires on world damage with nobody on (53 and 109 in the first half-minute), which is the cost the helicopter credit was expected to have. The other nine diff --git a/runicnpc/PLAN.md b/runicnpc/PLAN.md index 53e8495..c8584a9 100644 --- a/runicnpc/PLAN.md +++ b/runicnpc/PLAN.md @@ -142,6 +142,9 @@ architectural or design decision is implemented. | **D317** | **While Oxide's staging build is behind Rust's, the drill waits for it, for up to three days, opening nothing; after that it reports "Oxide has not caught up".** Oxide's build carries its own patched `Assembly-CSharp.dll`, the plugin compiles only against it, and a server on a pair that does not match dies at boot. "Behind" is measured, not guessed from dates: a type or member Rust's own assembly declares that Oxide's lacks (stage 9c; the org lead, from a concrete Monday-to-Wednesday example). | Running anyway and saying so; a publicizer step so the compile never depends on Oxide. | | **D318** | **The drill works only in the seven days before each monthly forced wipe (the first Thursday), not every day.** Rust changes on the forced wipe, so a week ahead leaves time to fix what breaks. In that week it checks each new staging build once, and D317's wait applies inside it; it is scheduled daily and stops at its first step outside the week. A run by hand always goes ahead. Narrows D308 (stage 9c; the org lead: "we can run the staging test pipeline like once a month about a week ahead to give lead time to fix it if it breaks but don't need it nightly"). | Nightly, as D308 had it. | | **D319** | **The staging rig is a 3500 map (seed 1234) with 8 GB, not 2000 at 6 GB.** Stage 5's five simultaneous fights need a 280 m line of open navmesh 300 m from every monument. Measured on built navmesh (20000 samples, the harness's own search), spots with that line at 300 / 250 / 200 m: 2000/1234 0 / 53 / 70; 3000/1234 0 / 0 / 8; 3000/981448696 0 / 0 / 6; **3500/1234 5 / 14 / 26**. Every other harness check passed on the 2000 map (226 of 227). The org lead first chose 3000, then, on those numbers, "bump it to 3500 … or drop to like 250m": 3500 has room at 300 m, so the clearance stays. A new 3500 map takes about 18 minutes to generate and build its navmesh; it peaked at 5.3 GB. Amends D307 (stage 9c). | A 200 or 250 m clearance on small maps; more seeds; skipping the check on small maps. | +| **D320** | **Rust's own animals count as animal kills again.** They are now `Rust.Ai.Gen2.BaseNPC2` (stag, boar, wolf, bears, big cats, crocodile, farm animals, critters, sea life), not the `BaseNpc` the bridge tested, so no animal kill counted as an animal or an NPC kill. Every `BaseNPC2` but `ScientistNPC2` is an animal; `ScientistNPC2`, the one person among them, is a human NPC kill. Found by the 9d server-side walk (stage 9d; the org lead: "Fix the animal kill things"). | Leaving it for later. | +| **D321** | **A heal on another player counts again.** `OnHealingItemUse` is `(tool, fromPlayer, toTarget)`; the bridge's two-parameter handler read the healer as the target and dropped every heal as healing yourself, so "Players healed" counted revives only. The bridge reads the target, and one act of help counts once whichever of its two hooks comes first (a syringe revive raises the heal first) (stage 9d; the org lead chose the fix from an example). | Only revives count; dropping the title. | +| **D322** | **An event's zone enforces its flags.** ZoneManager 3.1.14 subscribes the hooks behind its flags only for the zones it had when it loaded, so a zone opened later through its API enforced none of them: the walk shot a player dead in a PvpGod zone and built in a NoBuild one. RunicGatewayZones 0.2.0 has ZoneManager run its own `UpdateHookSubscriptions` a tick after any zone is made, the same moment it already enters the players inside (stage 9d; the org lead asked for the ZoneManager patch). | Patching ZoneManager's file; asking ZoneManager's author. | **Borrowing, not copying.** NpcSpawn states no licence at all, so its source grants us nothing and is read only as a description of *what* can be done in Rust. HumanNPC is MIT on uMod, which is GPL-compatible, but §1.2 rules out its @@ -1745,6 +1748,33 @@ far: - Stage 9's own: an event on a server without RunicNPC refused with its reason, and the installer's RunicNPC path from a real bundle. +**9d, the server side (2026-10-07).** The org lead asked for everything a server can prove to be done without +him, leaving the minimum to play. Rust 25681086, Oxide 2.0.7801, Carbon 2.0.262, the edge code; the walk site at +website `main` with module-rust `edge`. A rig-only plugin (never shipped) made **stand-in players** with +Steam-shaped ids that act through Rust's own code: a weapon's `ServerUse` or a player's hit report, a craft +finished, `Planner.DoBuild`, `DoRepair`, a medical tool's `GiveEffectsTo`, a mission's `MissionSuccess` and +abandon, an APC and a patrol helicopter brought down, `/link` through the bridge, and an NPC's hit landing on a +stand-in. Each result was then read on the site, in the browser and in the app. + +- **Passed, both rigs unless noted:** every §5.7 title condition but rockets and grenades (their hooks fire only + from a client); the death screen named by the profile (`AttackerInfo`); the killfeed's NPC and profile; a + stand-in linked through `/link`, its NPC kills on Player → Rust; the harness's commands, placements and floors, + with placements back after a restart; escort; a real clan as an ally, defended against a raider (Oxide); the + four example profiles on a fresh first boot (the staging rig); named markers and a bigger boss marker, and the + event page's lines in the browser and the app; an event on a server without RunicNPC refused with its reason. +- **Found and fixed:** D320 (animals), D321 (heals), D322 (an event zone's flags). Each was retested on both rigs: + two animal kills and a bandage and a syringe revive moved npcKills, animalKills and healed by exactly two each, + and an event's Arena zone, without the helper, let a player die and a foundation stand; with 0.2.0, the player + took no damage in forty hits and the foundation was removed. With the exemption the flags step aside (PvpGod by + ZoneManager's design needs both players exempt; NoBuild the builder). +- **Walk artifacts, not faults:** Rust's senses and `BasePlayer.FindByID` know only connected players, so a + stand-in is placed on the active list for a repair or helicopter credit, and an NPC's hit is landed for it; the + walk site's profile pushes replace the harness's set, so the harness runs with the site stopped. +- **Left for the player, one session per rig:** the death screen's portrait, the `/rnpc` replies in chat, the boss + bar and its lines, press E (Oxide), a locked crate's timer below zero, one rocket and one grenade, the zone + messages and the dome by day and night, and a chat title (Carbon, which has BetterChat). The installer's path + waits for the release (9e). + **9e. The cutover and release (D311, D313).** Only after the walk passes. In order: `runicnpc-rust` `edge` → `main` as v1.0.0 (its Gitea release is the only download); `rust-plugins`, `rust-link` and `installer` `edge` → `main`, and the bundle that pins all four; `module-rust` `edge` → `main` with RunicNPC required. Each release is checked diff --git a/rust-link/INSTALL.md b/rust-link/INSTALL.md index 9dda476..d5ab07f 100644 --- a/rust-link/INSTALL.md +++ b/rust-link/INSTALL.md @@ -164,8 +164,10 @@ history, and the token is unchanged. 3. **The plugin:** copy `runicgateway-rust-plugin/RunicGateway.cs` from the tarball into `oxide/plugins/` or `carbon/plugins/`, and every other `.cs` the tarball's `manifest.json` lists in `files` beside it — from protocol 13 that is `RunicGatewayZones.cs`, which lets ZoneManager count a - player already standing in a zone when it opens or comes back after a restart. It is optional: leave - it out and events still score everybody, but ZoneManager's own flags miss that player. The second is + player already standing in a zone when it opens or comes back after a restart, and (from 0.2.0) has + ZoneManager enforce the flags of a zone opened after it loaded. It is optional: leave it out and + events still score everybody, but ZoneManager's own flags miss that player, and an event zone's + flags such as PvpGod or NoBuild may not hold at all. The second is `RunicGatewayDomes.cs`, which lets the bridge ask ZoneDomes for a dome over an event zone; leave it out and zones open without one. To name the server, create its config first — `oxide/config/RunicGateway.json` or `carbon/configs/RunicGateway.json` — holding diff --git a/rust-link/PROTOCOL.md b/rust-link/PROTOCOL.md index 7ffc898..81fe256 100644 --- a/rust-link/PROTOCOL.md +++ b/rust-link/PROTOCOL.md @@ -2153,6 +2153,13 @@ connected player ZoneManager's own `IsPositionInZone` places inside, through Zon entry anyway. It fixes **every** zone on the server, not only the bridge's. It sweeps all zones once when it patches, and re-patches when ZoneManager reloads. `rgz.status` prints its state. +**From 0.2.0 it also has ZoneManager enforce a new zone's flags** (RunicNPC D322). ZoneManager subscribes +the hooks behind its flags (`OnEntityTakeDamage` for PvpGod and the other damage flags, `OnEntityBuilt` +for NoBuild, and the rest) only for the zones it had when it loaded, so a zone made later through its +API, which is every zone an event opens, enforced none of them. On the same tick it sweeps, the helper +calls ZoneManager's own `UpdateHookSubscriptions` (private; found by reflection). A ZoneManager without it +is warned about, and `rgz.status` says "new zones' flags NOT refreshed"; the first job still works. + The bridge never depends on it. `server.hello` carries, whenever ZoneManager is loaded: ```json @@ -2275,7 +2282,7 @@ frame from an older plugin has none: | Field | Counted from | Notes | |---|---|---| -| `animalKills` | `OnEntityDeath`, victim a `BaseNpc` | **Also** counted in `npcKills`, which keeps meaning every NPC and animal so the public column does not change mid-wipe. Human NPCs are the difference (D209) | +| `animalKills` | `OnEntityDeath`, victim a `Rust.Ai.Gen2.BaseNPC2` other than `ScientistNPC2`, or a `BaseNpc` | **Also** counted in `npcKills`, which keeps meaning every NPC and animal so the public column does not change mid-wipe. Human NPCs are the difference (D209). Rust's animals moved to `BaseNPC2` (stag, boar, wolf, bears, big cats, crocodile, farm animals, critters, sea life); until RunicNPC D320 the bridge tested `BaseNpc` alone and counted none of them. `ScientistNPC2` is a person: it counts in `npcKills` only | | `weaponKills` | every credited kill, any victim | By the **same weapon short prefab name the killfeed carries** (`WeaponPrefab`, else `Weapon`). The site classifies them into bow, melee, blade and revolver lists it keeps (§5.3), so a new Rust weapon needs no plugin release | | `headshots` | the killing blow's `HitInfo.isHeadshot`, any victim | | | `bestPvpM`, `bestPveM` | the killfeed's distance | **This interval's maximum, not a sum.** The site keeps the running best with `GREATEST`. PvE is NPCs and animals | @@ -2283,7 +2290,7 @@ frame from an older plugin has none: | `crafted` | `OnItemCraftFinished`, by `item.info.category` lower-cased | Every category is sent; the site reads `attire` and `weapon`. Counts `item.amount`. The crafter is `ItemCrafter.owner` (the task carries none) | | `built` | `OnEntityBuilt` placing a `BuildingBlock` | A deployable is not a structure | | `repaired` | `OnStructureRepair` | The hook fires on every hammer hit **before** the game decides there is anything to repair, the resources for it, or no recent attack. So the health is read then and again next tick, and only a hit that raised it counts | -| `healed` | `OnHealingItemUse` on **another** player; `OnPlayerRevive` | Yourself never counts (D174). A syringe on a downed player raises both hooks for one use; it counts once. The heal's target is an `IMedicalToolTarget` in current Rust, so the bridge takes it as `object` and counts only a player | +| `healed` | `OnHealingItemUse` on **another** player; `OnPlayerRevive` | Yourself never counts (D174). A syringe on a downed player raises both hooks for one use, the heal first; it counts once, whichever hook comes first. The hook is `(tool, fromPlayer, toTarget)`: until RunicNPC D321 the bridge took two parameters, read the healer as the target and counted no heal at all. The target is an `IMedicalToolTarget`, so the bridge takes it as `object` and counts only a player | | `rockets` | `OnRocketLaunched` | | | `explosives` | `OnExplosiveThrown`, `OnExplosiveDropped` | A `TimedExplosive` that deals damage: a supply signal and a smoke grenade go through the same hooks and do not count | | `missions` | `OnMissionSucceeded(mission, instance, assignee)` | Rust's own NPC missions (D173), credited to the hook's `assignee`. A failed or abandoned one raises a different hook and never counts |