CeruleanCutlass-NetKit icon

NetKit

Shared Photon co-op transport layer for Outward BepInEx mods: channels over one relay (or the RaiseEvent bus), a hello/peer ledger with absence detection, per-channel counters/trace/heartbeat, and PUN diagnostics.

Last updated 12 hours ago
Total downloads 3
Total rating 0 
Categories Libraries Utility
Dependency string CeruleanCutlass-NetKit-0.1.0
Dependants 1 other package depends on this package

This mod requires the following mods to function

BepInEx-BepInExPack_Outward-5.4.19 icon
BepInEx-BepInExPack_Outward

BepInEx pack for Outward.

Preferred version: 5.4.19
CeruleanCutlass-ForgeKit-0.1.0 icon
CeruleanCutlass-ForgeKit

Dependency-free dev-tooling for Outward BepInEx mods: file-driven dev command loop, self-test harness, on-screen toasts, player-ready lifecycle wait, and embedded/override table loaders.

Preferred version: 0.1.0

README

NetKit

📖 Full documentation: NetKit wiki page

The shared Photon co-op transport layer for the Outward mod kits. It owns the transport shell CompanionKit's NetBus and SpawnKit's SpawnNet used to duplicate (~65–75% line-identical): relay attach, send helpers, the one hello + peer ledger + absence detector, per-channel+verb counters and trace, join/leave hooks, heartbeat, and the PUN diagnostics. Message semantics (verbs, codecs, authorization, flush policies) stay in the consumer mods.

Full design + decisions: docs/netkit-plan.md. Background: docs/photon-layer-research-2026-07-18.md.

What it owns

  • Channels, not per-mod buses. One shared NK_Bus wire envelope (channel, verb, seq, extra, payload) carries every channel; the demux is channel → verb.
  • One hello / one absence detector. nk.hello on join/connect/guest-scene-ready carries the proto version + the registered channel→version map + optional per-channel extension strings. Peer state surfaces per channel (OnPeerReady / OnPeerLost / IsPeerReady / ReadyCount). The 10 s "peer appears UNMODDED" warning lives here once.
  • Diagnostics under the owning channel's log tag: counters, ring-buffer trace, heartbeat (all PhotonNetwork.time-stamped), the PUN log-signature watcher + per-id unknown-view table. netdump reports transport/attach/peers/counters; selftest runs the loopback + Core checks. The per-channel heartbeat line carries cumulative tx=/rx=/drops= totals (after pt=, before the consumer fragment) so the passive stream survives when the interactive dumps are unreachable.

Dev verbs (BepInEx/config/NetKit_cmd.txt)

  • netdump — co-op census on this machine (transport/attach, per-channel verbs/peers/counters/ ring buffer, hello ledger, PUN-signature + unknown-view tables, helloMuted).
  • selftest — [SELFTEST] PASS/FAIL … DONE: hello codec, compat calc, peer-ledger timing, counters, and (in a room) the transport loopback.
  • netmute [on|off|status] (bare = status) — ON suppresses OUTGOING nk.hello sends so this box reads as UNMODDED to peers; the staging tool for incompatible-peer testplan rows without renaming a DLL mid-session. Incoming handling unchanged. Session-only (not persisted), default off.
  • Pure half in core/NetKit.Core (unit-tested, zero game refs): hello codec (lossless escaping), channel-version compat, the peer-ledger timing, counters/trace models, heartbeat formatter, unknown-view table.

API sketch (game side)

var ch = Net.RegisterChannel("sk", "0.4.0", new ChannelOptions {
    LogTag = "SKNET", HelloExtension = () => …, HeartbeatFragment = () => … });

ch.Register("spawn", msg => …);          // msg: Verb, Payload, Extra, SenderActor,
                                         //      SenderIsMaster, SenderIsSelf
ch.SendToMaster("hit", payload, extra);  // bool; also SendToOthers/SendToAll/SendToPlayer
ch.SendToAllLoopback("nk.test", …);      // selftest
ch.OnPeerReady += info => …;             // info: Actor, ChannelVersion, Extension, IsMaster
ch.CountDrop("spawn", "no-row");         // consumer-reported drops

Net.Attached / Net.InRoom / Net.IsMaster / Net.IsGuestInRoom

Send guards return false + drop-count (never throw). Unknown verb on receive = warn-once + counter. Handlers run inside try/catch with per-verb error counters.

Two backends (one internal transport interface)

  • Rpc (default): the NK_Bus [PunRPC] relay piggybacked on CharacterManager's PhotonView (viewID 999). RefreshRpcMonoBehaviourCache() after attach is a harmless no-op precaution — the shipped UseRpcMonoBehaviourCache is false, so it is not "mandatory".
  • Event (opt-in): PhotonNetwork.RaiseEvent on one configurable code ([Net] EventCode, default 177) — no GameObject, no view. Implemented + selftest-covered but config-gated OFF ([Net] Transport = Rpc) until its offline loopback + two-box behavior are live-verified.

Example configuration

BepInEx/config/cobalt.netkit.cfg — created on first launch. Excerpt (defaults):

[Net]
## Transport backend: Rpc (default) or Event.
Transport = Rpc
## RaiseEvent code used by the Event backend.
EventCode = 177
## Seconds between per-channel heartbeat lines.
HeartbeatSeconds = 30
## Seconds before a silent peer is warned as "appears UNMODDED".
HelloWarnSeconds = 10
## Per-message send/receive logging under each channel's tag.
VerboseNet = true
## Photon DisconnectTimeout override in ms; 0 = leave the game's default.
DisconnectTimeoutMs = 0

The dev command channel is BepInEx/config/NetKit_cmd.txt (the verbs above). NetKit ships no config-override data tables.

Cutover note

New builds speak NK_Bus only. An old-build peer (CK_Bus/SK_Bus) looks unmodded to the new hello and is refused co-op cooperation by the existing incompatible-peer machinery — acceptable because the mods ship as one bundle (Outward-Mods-latest.zip). W2/W3 refactor CompanionKit's NetBus and SpawnKit's SpawnNet onto channels ck / sk; nothing ships live-verified until a real two-box session per project rules.