docs(runicnpc): 9e step 1 — dome stack 3 and the PopupNotifications banner (D326–D328) #330

Merged
whitlocktech merged 1 commits from docs/runicnpc-9e-step1 into main 2026-10-09 06:29:13 +00:00
4 changed files with 81 additions and 4 deletions

View File

@@ -470,7 +470,10 @@ Built on `edge` as Rust-Plugins `feat/zones-domes`, Module-Rust `feat/zones-dome
Green and Purple are the Twitch battle-royale spheres, which ZoneDomes itself says show only where they meet
terrain or an object, so they read as a ring at the zone's edge. **The org lead chose the full dome as the
default:** the step lists Standard first as "Full dome (shaded)", labels each colour for what it shows, and
a `dome` sent without a `type` is Standard. The default stack is still 1, until it is chosen by eye.
a `dome` sent without a `type` is Standard. **The default stack is 3** (D326, `runicnpc/PLAN.md`): the org
lead chose it by eye at noon in RunicNPC's 9d player session, and the module sends it when the step's stack
is blank. Steps saved with a blank stack draw 3 from their next run. The wire is unchanged: a `dome` sent
without a `stack` still gets 1 from the bridge, and the module always sends one.
**Compiled on both rigs, and the rig rows walked, 2026-09-29** (no player; the "file API outage" that
morning was a Git Bash path-rewriting bug in the walk's own helper, not the rigs):
@@ -540,7 +543,8 @@ so the site refuses it until protocol 13 ships:
- **The in-game rows, deferred by the org lead (2026-09-29) to the later in-game walk, with §5.7's:**
- The flags hold for a player without the exemption and not for one with it.
- The enter and leave messages arrive as chat and as a popup.
- The default stack is chosen by eye, in daylight and at night.
- ~~The default stack is chosen by eye, in daylight and at night.~~ Chosen at noon in RunicNPC's 9d
player session: 3 (D326).
---

View File

@@ -148,6 +148,9 @@ architectural or design decision is implemented.
| **D323** | **Each example profile wears its own outfit; the weapons of D289 stay.** The raider wears scrap armour (coffee-can helmet, roadsign jacket and kilt, hoodie, pants, boots, leather gloves); the camp guard metal (metal facemask and chest plate, long-sleeve shirt, pants, boots, roadsign gloves); the sniper hide (boonie hat, balaclava, hide poncho, pants and boots); the Juggernaut heavy plate, as before. Three shared one hoodie, pants and boots, so they read alike in game. Only a first load writes them (D284): no live server has RunicNPC yet, and the test rigs were given them by resetting `examplesWritten` (9d player walk, 2026-10-09). | One outfit for all; replacing a server's kits that still match the old ones. |
| **D324** | **An example kit's items are written at full durability, read from this Rust's item definitions when the kits are written.** Kits sets an item's condition and maximum straight from its file. The writer put 0 on armour (a metal facemask 0 of 320, heavy plate 0 of 1,000, so it barely protected) and 100 on every weapon whatever its real maximum (an M249's is 500). Now an item with a condition gets the definition's maximum and one without gets 0. The harness checks it (`s6.examples.fullCondition`). | A fixed table of maximums, which a Rust update would make stale. |
| **D325** | **`/rnpc here` stands the NPC 1 m in front of the admin, facing them, not at their feet.** At their feet it spawned inside the admin, looking away (9d player walk). The spot must fit: nothing at chest height in the way, ground no more than 2 m below, and navmesh for one that walks. Otherwise it falls back to the admin's own spot and the reply says so. The harness checks the distance (`cmd.chat.here`). | 1.5 m or 3 m in front; refusing when the spot does not fit. |
| **D326** | **An event zone's dome stacks 3 spheres when its step leaves the stack blank, not 1.** The org lead chose it by eye at noon in the 9d player session. It changes in `module-rust` only, so steps saved with a blank stack draw 3 from their next run. The wire is unchanged: the bridge still gives a `dome` sent without a `stack` 1, and the module always sends one (2026-10-09). | The bridge's default too, which changes a message's meaning and needs a plugin release. |
| **D327** | **A recommended PopupNotifications look ships three ways:** a banner across the top of the screen (left 0.15, bottom 0.87, width 0.7, height 0.07, spacing 0.005), no close button, Roboto Condensed Bold at 18, panel transparency 0.8. It is a block to paste in `rust-link/INTEGRATION.md` §2.4, and both the installer (`install` and `update`) and the Pterodactyl egg's install write it. Each writes it with PopupNotifications' `Version`, because the plugin resets a config without one. PopupNotifications stays optional and is the operator's to install (D141). The org lead: whether `update` also writes it "does not matter", since no host outside the test rigs is installed yet. | Writing it on a first install only; writing it once per host and recording that. |
| **D328** | **"Still at the plugin's defaults" means every setting, all or nothing.** The installer and the egg write the banner only where `PopupNotifications.json` is missing, or where every setting equals PopupNotifications 0.2.1's own defaults (its `Version` aside; numbers compared as numbers). A config with any one setting changed, or one that does not parse, is kept whole and never merged into. | Field by field: our values only where that one field is still at its default, which mixes two looks nobody chose. |
**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
@@ -1825,6 +1828,23 @@ put three steps in 9e, in this order (2026-10-09), each ending in its PRs:
`module-rust` `edge` → `main` with RunicNPC required. Each release is checked (checksums, the bundle composing)
before the next merge. The row the walk left for it: the installer's RunicNPC path from a real bundle.
**9e step 1, as built (2026-10-09; D326–D328).** Before building, the org lead answered three questions. The dome change
is in the module only. "At defaults" means all settings or none. Whether `update` also writes the banner "does not matter",
since no host outside the test rigs is installed yet, so both verbs apply the rule.
- **Dome stack 3:** Module-Rust#36. A blank stack sends 3, and the field's hint says so. 505/505 tests pass.
- **The banner, three ways:** the paste block in `rust-link/INTEGRATION.md` §2.4; installer#39 (`popup.rs`; a
`popup look` row in the plan; `install` and `update`; 188 tests pass, 5 of them new); Rust-Link#22 (`rg_popup` in
`egg/install.sh`; a failure there is said in the console and does not fail the install). Both write PopupNotifications'
`Version`, because the plugin resets a config without one, and keep a later version they find.
- **How it was checked:**
- **The installer:** run against two scratch roots. No published bundle carries RunicNPC yet, so the run bypassed its
check in the working tree only. Oxide with no config got the banner. Carbon at the rig's defaults got the walk's
banner file exactly. On `update`, a hand-changed config and an existing banner were both kept.
- **The egg's function:** run in `ghcr.io/ptero-eggs/installers:debian` over the same cases, plus a config that is
not JSON and a directory that cannot be made.
- **Left for step 3:** both paths from a real bundle (the installer on a rig, the egg by a panel reinstall).
**Then the Rust plan resumes** at PLAN_REDESIGNS §9 item 6 (the step editor and the kit weekend) and §11.
### Stage 10 — Custom navigation (only if needed)

View File

@@ -77,6 +77,12 @@ What a run does:
The plugin fills in every other key on its first load. An existing config is never rewritten,
and one that names a different server stops the run and says so — the website locks a server's
id once it has seen it.
It also writes the **recommended PopupNotifications banner** (`PopupNotifications.json` beside it;
[`INTEGRATION.md` §2.4](INTEGRATION.md#24-the-recommended-popupnotifications-look-optional)), but
only where that file is missing or still exactly the plugin's own defaults. A config with any
setting changed is kept whole. `install` and `update` both apply this rule, and the plan names
which case it found (`popup look … banner written` or `kept`). PopupNotifications itself stays
optional and yours to install. One that is already running shows the banner after its next reload.
4. **Installs the sidecar**, writes its config with this server's ports, and registers its service.
5. **Places the plugin** in `oxide/plugins/` or `carbon/plugins/`. Both frameworks load a plugin
the moment it lands, so on a running server the bridge comes up at once; on a stopped one, at
@@ -125,7 +131,10 @@ When you create a server from it:
against the bundle's checksum, and only then places them: the sidecar in `rust-link/`, the plugin in
`oxide/plugins/` or `carbon/plugins/`. `RunicNPC.cs` goes beside the plugin (its data directory is left for RunicNPC to make); a bundle
without RunicNPC is refused. Any mismatch fails the install with the reason, before
anything is placed.
anything is placed. It then writes the recommended PopupNotifications banner by the installer's
rule: only where `PopupNotifications.json` is missing or still the plugin's defaults, so a look
you chose survives a reinstall. If that write fails, the console says so and the install still
succeeds, because it only affects how popups look.
**The first boot** prints, in the console, the token the sidecar generated — **once** — and a line
with what the website needs:
@@ -230,7 +239,7 @@ With the installer:
|---|---|
| `doctor --game rust [--server-id <id>]` | Per server: the framework; whether the plugin file is still the one deployed; each helper deployed beside it, as a **warning** when missing or edited (the bridge runs without one, and the row says what that costs); RunicNPC as a **failure** when it is missing or was never installed (it is required: without it every NPC an event places is refused), and a warning when edited by hand; that the plugin's config names this server; the required uMod plugins; the service; and `/health` through to **plugin connected**. A stopped server is a warning; a running one whose plugin never connected is a failure, printed with the framework versions the plugin is known good on |
| `update --game rust` | Moves the sidecar and every server's plugin to the current bundle, and restarts the sidecars. Always all servers together — they share one binary |
| `uninstall --game rust [--server-id <id>] [--purge]` | Removes the service, the plugin file, its helpers and RunicNPC. **Keeps the plugin's config** and RunicNPC's `data/RunicNPC/` (an admin's placements and routes) — it is the website's, and it names the server. `--purge` also removes the sidecar config (the token) and the database. Removing the last server removes the shared binary too |
| `uninstall --game rust [--server-id <id>] [--purge]` | Removes the service, the plugin file, its helpers and RunicNPC. **Keeps the plugin's config** and RunicNPC's `data/RunicNPC/` (an admin's placements and routes) — it is the website's, and it names the server. `--purge` also removes the sidecar config (the token) and the database. Removing the last server removes the shared binary too. `PopupNotifications.json` is never touched: it belongs to PopupNotifications |
With the egg: reinstall to update (above); the console is the diagnosis.

View File

@@ -137,6 +137,50 @@ Then press **Test**, which probes the sidecar and reports what came back:
Within a poll interval the server appears at `/rust/servers`.
### 2.4 The recommended PopupNotifications look (optional)
PopupNotifications is optional (D141). Where it is installed, news, events and zone messages can
arrive as popups, and its stock look is a small grey box at the right of the screen, sized for one
short line. The look we recommend is a **banner across the top of the screen**: no close button,
Roboto Condensed Bold at 18 (chosen in RunicNPC's 9d player session, D327). Paste this over
`oxide/config/PopupNotifications.json` (Carbon: `carbon/configs/PopupNotifications.json`), then
`oxide.reload PopupNotifications` (Carbon: `c.reload PopupNotifications`):
```json
{
"Notification duration (in seconds)": 8,
"Maximum notifications shown at any time": 6,
"UI Positioning": {
"Position of the left side of notification (0.0 - 1.0)": 0.15,
"Position of the bottom of noticiation (0.0 - 1.0)": 0.87,
"Width (0.0 - 1.0)": 0.7,
"Height (0.0 - 1.0)": 0.07,
"Space between notification (0.0 - 1.0)": 0.005
},
"UI Options": {
"Show close button": false,
"Panel color (hex)": "#2b2b2b",
"Panel transparency (0.0 - 1.0)": 0.8,
"Close button color (hex)": "#d85540",
"Close button transparency (0.0 - 1.0)": 0.5,
"Font": "robotocondensed-bold.ttf",
"Font size": 18
},
"Version": {
"Major": 0,
"Minor": 2,
"Patch": 1
}
}
```
- **Keep the `Version` block.** PopupNotifications resets a config with no version (or one below
0.2.0) to its defaults when it loads, which would undo the banner.
- The key `noticiation` is PopupNotifications' own spelling. Leave it as it is.
- **The installer and the Pterodactyl egg write this for you**, but only where the file is missing
or is still exactly the plugin's own defaults (D328). A config with any setting changed is kept
whole, so a look you chose is never replaced. See [`INSTALL.md`](INSTALL.md).
---
## 3. When it does not work