docs(modules): the Carbon rig, R19 proven, and one refuted claim

rust-carbon (id 18, 87fb1f67) is built on the same egg with FRAMEWORK=carbon,
a clean install rather than a converted one. Install 122s, boot 543s.

R19 is proven. The byte-identical RunicGateway.cs that runs on the Oxide rig,
with no conditional compilation in it at all, loaded on Carbon 2.0.259.0 and
behaved identically - same startup line, same no-stall retry against an absent
sidecar. That contract now holds on three platforms from one source file:
Windows/Mono, Linux/Oxide, Linux/Carbon.

R18's amendment is confirmed the best way it could have been. The plugin's own
config, written by the same Oxide-compat API on both rigs, landed at
oxide/config/RunicGateway.json on one and carbon/configs/RunicGateway.json on
the other. Had the R18 editor used a literal path it would not have found its
own plugin's config on half of all installs, and no test would have caught it.

One claim is REFUTED, and it was wrong about Oxide as well as Carbon. CARBON.md
said Oxide stores permissions as JSON and Carbon as Protobuf or SQLite. Both
rigs say otherwise: both store Protobuf, under IDENTICAL filenames
(oxide.users.data, oxide.groups.data), differing only in directory - Carbon
writes its own data into files named after Oxide. This strengthens R2's
API-only rule rather than weakening it: a file reader would have worked on both
rigs today and broken silently for the one operator who ran c.migrate_perms_sql.

Two things nobody had thought to claim, found by looking. Carbon auto-creates a
third default group, moderator, which R2's full-set push must tolerate or
report drift for ever. And c.plugins exposes per-plugin hook telemetry that is
most of section 6's fired-hook mechanism, free, and only on Carbon.

Also: a wrong console command is indistinguishable from success at the API.
Pterodactyl's command endpoint returns 204 either way and Carbon prints nothing
for an unknown command, so oxide.plugins on Carbon looks exactly like it
worked. Anything driving the console must read a log to know.

CARBON.md gains a scorecard (section 10) and drops its unproven banner.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
This commit is contained in:
2026-09-15 21:51:11 -05:00
parent 069e715b1b
commit 22159ec78f
2 changed files with 150 additions and 18 deletions

View File

@@ -1763,15 +1763,15 @@ including one finding that changes R20's shape.
| | |
|---|---|
| Panel | `http://192.168.0.12` (no TLS — `https://` fails outright), node 1 `Main` |
| Server | `rust-oxide`, id **17**, identifier **`e6758c06`** |
| Egg | 18 `Rust Autowipe`, `ghcr.io/pterodactyl/games:rust` |
| `FRAMEWORK` | `oxide` |
| Servers | `rust-oxide` id **17** / **`e6758c06`**, and `rust-carbon` id **18** / **`87fb1f67`** (§14.5) |
| Egg | 18 `Rust Autowipe`, `ghcr.io/pterodactyl/games:rust` — both rigs, one egg |
| `FRAMEWORK` | `oxide` / `carbon` |
| Limits | 8192 MB memory, 25600 MB disk — deliberately under half the node, so the Carbon rig fits beside it |
| Allocations | 21000 game (default), 21001 query, 21002 RCON, 21003 Rust+, **21004 held for the sidecar's `[web].bind`** |
| Allocations | oxide 21000-21004, carbon 21005-21009 — game, query, RCON, Rust+, **and one held for the sidecar's `[web].bind`** |
| World | procedural, size 3000, seed 1234 |
| SFTP | `192.168.0.12:2022` |
The RCON password is a generated 24-byte token rather than the old rig's `letmein`, kept out of this
The RCON passwords are generated 24-byte tokens rather than the old rig's `letmein`, kept out of this
document and out of the repo. §6 named that shape as the thing nothing published should copy; this is
the first rig where it was not copied.
@@ -1943,6 +1943,67 @@ string is composed so that whatever the entrypoint prepends still lands on `Rust
is more work and survives upstream changing its entrypoint; the second is free and depends on a line
in somebody else's repository. Raised rather than settled.
### 14.5 The Carbon rig, and R19 proven
`rust-carbon` — id **18**, identifier **`87fb1f67`**, same egg, same world (procedural, 3000, seed
1234), same limits, allocations **21005-21009 with 21009 held for the sidecar**, `FRAMEWORK=carbon`.
**A clean install, never a converted one**, per R21: install 122s, boot 543s, running.
**R19 is proven.** The byte-identical `RunicGateway.cs` that runs on the Oxide rig — no `#if CARBON`
anywhere in it, nothing conditional at all — loaded on Carbon **2.0.259.0** and behaved the same:
```
[INFO] Carbon 2.0.259.0 [2026.09.03.0] 21063e8 on Linux
[INFO] [Runic Gateway] protocol 1, serverId 'main', sidecar 127.0.0.1:7799
[INFO] Loaded plugin Runic Gateway v0.1.0 by RunicGateway [2367ms]
[INFO] [Runic Gateway] cannot reach the sidecar: Connection refused - retrying quietly [RunicGateway Link|26]
```
That last line is the no-stall contract holding on its **third** platform now — Windows/Mono,
Linux/Oxide, Linux/Carbon — from one source file. [`CARBON.md`](CARBON.md) §10 is the full scorecard;
the parts that change decisions are below.
**R18's amendment is confirmed the best way it could have been.** The plugin's own config, written by
the same Oxide-compat API on both rigs, landed at `/oxide/config/RunicGateway.json` on one and
**`/carbon/configs/RunicGateway.json`** on the other. D3 put the plugin's config inside R18's editor;
had that editor used a literal `oxide/config/`, **it would not have found its own plugin's config on
half of all installs.** No test would have caught it; only two rigs would.
**And one claim was refuted — the one with the sharpest consequence.** `CARBON.md` had said Oxide
stores permissions as JSON and Carbon as Protobuf or SQLite, offering the difference as the reason
not to read the file. Both rigs say otherwise:
| | Oxide rig | Carbon rig |
|---|---|---|
| Path | `oxide/data/oxide.users.data` | `carbon/data/`**`oxide.users.data`** |
| First bytes | `0a 16 0a 07 64 65 66 61 75 6c 74` | `0a 17 0a 07 64 65 66 61 75 6c 74` |
| Format | Protobuf | Protobuf |
| Default groups | `default`, `admin` | `default`, `admin`, **`moderator`** |
**Same binary format, same filenames, different directory** — and Carbon writes *its* data into files
named after Oxide. **This makes R2's API-only rule more important, not less.** A file reader would
have worked on both rigs today and broken silently for the one operator who ran `c.migrate_perms_sql`
— no error, no version marker, just a site reporting drift against a store nobody is writing any
more. The rule survives; the reasoning behind it was wrong and is now right.
Two things nobody had thought to claim, found by looking:
- **Carbon auto-creates a third default group, `moderator`**, auto-granted by auth level alongside
`default` and `admin`. R2 pushes its *full* set on connect, so it has to tolerate a group the
framework will recreate the moment it is deleted — otherwise the site reports drift for ever.
- **`c.plugins` reports per-plugin `hook fires`, `hook time`, `hook memory`, `hook lag` and
`hook exceptions`** — most of §6's "log which expected hooks have fired" mechanism, free, and only
on Carbon. Useful when debugging on Carbon; **not a substitute** for the plugin's own counter, which
must work on both.
**A warning about how a wrong console command fails here.** Pterodactyl's `command` endpoint returns
`204` whether or not anything happened, and Carbon prints nothing for an unknown command. So
`oxide.plugins` on Carbon — which is simply not a command — is indistinguishable from success at the
API. Anything driving the console has to read a log to know, which is the same conclusion §14.2c
reached about `oxide.reload` and the same shape R18's rollback needs.
---
[rl]: https://gitea.whitlocktech.com/RunicGateway/Rust-Link