Shared Photon co-op transport layer for Outward BepInEx mods: channels over one relay, a hello/peer ledger, per-channel counters/heartbeat, PUN diagnostics, and a replicated-record store for consumer state mirroring.
W4: fixes for everything D1 found, plus the join-race P1
MP fix waves W1/W2/W3, and what Block A found when we ran them
Log levels: a per-mod [Diag] LogLevel, gating at the source
NetKit + CompanionKit: GetComponent ?? AddComponent, without the eager trap (UNT0007)
Cloudward + NetKit: say it when a decision was downgraded
Cloudward + NetKit: five ways a quiet failure became a loud one
Unreleased
New (built, NOT live-verified): ChannelOptions.QuietVerbs — verbs a channel declares quiet
skip the per-send/per-recv LogInfo line AND the 32-entry TraceRing write, while their
COUNTERS still count (sends/recvs/drops stay in netdump). Built for streaming verbs
(CompanionKit's ~5 Hz ck.proxy.pos would wrap the whole trace ring in ~6 s and destroy the
cross-verb forensic window, and would drown a VerboseNet log). Pure membership decision is
NetKit.Core.QuietVerbSet (ordinal, null/empty-tolerant, unit-tested).
New: NetKit.Views — a shared clone/view-hygiene API, positioned as NetKit owning the Photon
half of "clean up a live clone" so consumers stop hand-rolling their own tripwires against it.
Neutralize performs the always-legal PhotonView field writes (removedFromLocalViewList,
viewID=0, sync mode Off, send group 254) BEFORE any destroy is attempted, so even a refused
destroy can no longer let a stray view evict the real replica underneath it. VerifyClean /
CountNetwork / DisarmSurvivors round out an honest report of what actually survived a strip,
rather than assuming a logged destroy call succeeded.
New: core/NetKit.Core/ViewHygiene.cs — the pure compute half (ViewFacts/SurvivorFacts/
Describe/IsClean), including the one shared RefusalNote const for "Unity silently refuses
this class of destroy call here" so the explanation can't drift between call sites that quote it.
New: NetKit.ViewLease — the view LIFECYCLE complement to Views (phase 2 of the hygiene
migration; built, NOT live-verified): Mint/Bind allocate or adopt a viewID onto an inactive
clone's own PhotonView (single-view semantics; no-view is a returned fact, never a throw —
the consumer owns the warn-vs-refuse policy), Release/DeferRelease/SweepPending park a
minted id next to its body and hand it back only once the body is destroyed (PUN warns on a
live-view release), and MuteView + MutedGroup (253) stop a registered view from streaming
WITHOUT deregistering it — deliberately the opposite doctrine of Views.Neutralize (254), the
header explains why both groups coexist. One instance per consumer: the ledger, the log tag and
the once-per-session latches are consumer-scoped, and every emitted line is byte-stable with
the pre-migration SpawnKit implementation; consumer-dialect notices surface through callbacks.
New: core/NetKit.Core/ViewLease.cs — the pure half of the lease (LeasePolicy with the 300s
age-out rule and the aged-out re-check verdict, ViewLedger/LeaseEntry the pending-release
ledger, MuteResult), unit-tested for the first time incl. the byte-stable forensics dump
format (ViewLeaseTests).
0.2.2 — 2026-08-02
Cloudward + NetKit: say it when a decision was downgraded
Cloudward + NetKit: five ways a quiet failure became a loud one
Docs sweep: archive, condense, and validate the whole documentation tree