DonorKit
The donor-scene harvest engine for Outward BepInEx mods: additively load any area, clone a live creature into a dormant template, and unload cleanly. Library dependency, installed automatically when another mod (SpawnKit, CompanionKit) needs it.
| Date uploaded | 2 days ago |
| Version | 0.1.5 |
| Download link | CeruleanCutlass-DonorKit-0.1.5.zip |
| Downloads | 42 |
| Dependency string | CeruleanCutlass-DonorKit-0.1.5 |
This mod requires the following mods to function
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, embedded/override table loaders, and a shared dev-verb pack (movement/combat/skill/status probes).
Preferred version: 0.4.4CeruleanCutlass-NetKit
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.
Preferred version: 0.2.4README
DonorKit
📖 Full documentation: DonorKit wiki page
The donor-scene harvest engine — "get me a live body for species X, from anywhere in the game". Extracted from CompanionKit on 2026-07-26 (static-analysis wave, lane 5E) so SpawnKit no longer has to depend on the pet library for its creature supply.
Requires: BepInEx 5 (BepInExPack_Outward), ForgeKit and NetKit — both hard
dependencies ([BepInDependency]), so BepInEx refuses to load DonorKit if either folder is missing
from BepInEx/plugins/. NetKit is needed for the auto-warm room-left re-arm. No SideLoader;
DonorKit builds on a clean runner.
What it owns
DonorHarvest— the guarded cycle: additive load → freeze atsceneLoaded→ payload → unload → repair. GenericFunc<Character, object>/Func<Scene, object>payloads, so one load can stock many species (light-probe cycles are the scarce resource).DonorSceneTable(+ the embeddedDonorScenes.txt) — the species→scene roster, pins, identity resolution, and build-scene name matching.DonorScenes.txtis generated bybwspecies build; seetools/BwSpeciesTool/Paths.cs.DonorCreatureFinder— exact-then-substring live-donor lookup within a loaded scene.- The guards, each of which exists because the un-guarded version broke a real session:
DonorPhotonGuard(view-id collisions),DonorSaveGuard(mid-harvest saves, deferred via the game's ownSetSaveRequiredre-arm),DonorSingletonRestore(the fourInstance-hijack restores),AmbienceGuard+DonorAudioRegistry(the persistent audio registries),SoulSpotGuard(duplicate-UID throw mid-Awake),DonorDisplayRepair(skybox / display chain),TerrainGuard(LOD render holes),DonorNetworkInitGuard(guest replica conversion). SkeletonRig/RagdollRig— the rig surgery a dormant clone needs to render and ragdoll.- The expedition tier (lane 5E-2, 2026-07-26 — moved here from CompanionKit):
ExpeditionHarvest(the vanilla-loader round-trip state machine for region/town scenes too large to load additively),ExpeditionOrchestrator(theexpeditionverb body, batch capture payload, in-place capture, once-per-launch boot auto-warm),ExpeditionManifest(ck_expeditions.txt— the historical filename is kept so existing installs keep their record),StorySense(the prologue/first-visit story-safety reads). Config:[Expedition]incobalt.donorkit.cfg. Theexpedition/templateclear/expeditionreset/templateprobeverbs stay on CONSUMER channels (ck_cmd.txt+ BW twins) —templateclear's full reset must also re-arm CompanionKit's effigy gate, which only the consumer can reach. - The K2 template store (
TemplateStore+BodyTemplateStore, lane 5E-2): ONE store with per-consumer KINDS. The companion-body kind (BodyTemplateStore) carries identity + captured stats, eager-prunes, refuses substitutes on identity lookups, keeps unhealthy rigs; the spawn kind (SpawnKit'sSpawnTemplateslayers on it) holds bare normalized GameObjects with opt-in LRU cap eviction and rejects unrepairable rigs. The kinds share only the FABRIC (holder/registry/recency/ orphan-proof clear) — unifying the policies would change behavior, so they stay at their call sites. E3 seams for third mods:TemplateStore.RegisterSpecies(key, scenes)(runtime donor-table row) andTemplateStore.RegisterTemplate(key, dormantBody)(parks a body in the spawn kind through SpawnKit's own normalization seam).
What it deliberately does NOT own
- Puppet-shaped code (the 5E permanent-shim rule — a DonorKit→CompanionKit edge is forbidden):
BodyTemplateCache.PuppetFrom(template →CompanionBodyviaBodyFactory.BuildPuppet),ClearAllAndForgetMisses(also resets the effigy harvest gate), and the stat capture itself — the store calls back throughBodyTemplateStore.StatCapturer, which CompanionKit wires toAttributeCapture.Fromin its Awake. - The pure donor logic —
DonorTable,DonorChain,RigSurgery,TerrainMaintenance, theExpedition*planners — which stays incore/CompanionKit.Core.DonorTablecannot be split fromSpeciesTable/Species/Regions, and its file-matesSpeciesFloats/SpeciesYaware consumed byBeastwhispering.Core. DonorKit references that game-ref-free COMPUTE assembly, never the CompanionKit plugin.
Dev verbs (BepInEx/config/DonorKit_cmd.txt)
photondump · audiodump · audioprune · terraindump · terrainfix · scenedump · selftest
Consumers also register the first five on their own channel via DonorVerbs.RegisterAll(registry).
Config
BepInEx/config/cobalt.donorkit.cfg, created on first launch. Two sections, eight keys, all shown
here at their shipped defaults:
[Rig]
## Rebuild a dormant clone's skinned-bone bindings so it renders. Moved here from
## cobalt.companionkit.cfg along with SkeletonRig, its only reader.
RepairSkinnedBones = true
[Expedition]
## Run the capture payload in place when you enter an oversized scene that still has
## uncached species. OFF by default — it is the only path where the kit does heavy
## work nobody asked for.
CaptureOnSceneEntry = false
## One-time template warm at the first player-ready of a launch: needed | all | off.
AutoWarmAtBoot = needed
## Comma-separated species warmed regardless of the mode. Empty = the species installed
## consumer mods register in code; a non-empty list REPLACES those; "none" disables all warming.
AlwaysWarmSpecies =
## How long the boot warm may wait for a busy loader before giving up (0 = don't wait).
AutoWarmRetrySeconds = 60
## Safety net for the RETURN leg — re-request the trip home if no load has started.
ReturnRetrySeconds = 20
## Allow an expedition to start with guests connected. OFF: each leg ends in a per-machine
## continue gate nothing host-side can pass, and one AFK guest wedges the whole party.
AllowCoop = false
## Guest-side auto-pass for those gates while a host-run expedition is in flight. Inert
## otherwise, so it is safe to leave on.
GuestAutoContinue = true
Each generated entry carries its own # Default value: comment and a much longer description than
the summaries above. BepInEx never migrates a changed default into an existing cfg — if
behaviour doesn't match, compare against those comments before assuming a bug.
Expedition manifest — BepInEx/config/ck_expeditions.txt (the historical filename is kept so
existing installs keep their record). âš Known bug (docs/expedition-harvest-testplan.md V42,
confirmed live 2026-07-25, open): the manifest reports captured scenes in memory but the file is
never written, so the record does not survive a relaunch.
API sketch
[BepInDependency("cobalt.donorkit", BepInDependency.DependencyFlags.HardDependency)]
if (DonorHarvest.TryGetDonorScenes("Pearlbird", out var scenes, out var term))
StartCoroutine(DonorHarvest.HarvestChain(scenes, term,
use: src => Object.Instantiate(src.gameObject, MyHolder().transform),
onResult: r => UseOrFailLoudly(r as GameObject)));
The payload runs while the donor is loaded and frozen, must be synchronous, and must not keep
references into the donor scene. Use DonorHarvest.IdentityFor(key, src) rather than src.Name —
a pinned row's frozen donor reports its serialized base-prefab name.
CHANGELOG
DonorKit changelog
0.1.5 — 2026-08-19
- Ragdoll restore: judge by the donor's manager caches, never the clone's joint count
0.1.5 — 2026-08-19
- Ragdoll restore: judge by the donor's manager caches, never the clone's joint count
0.1.4 — 2026-08-19
- Fix V6b: the Quiet-tier exemption was inverted (ambient ungated, verbs gated)
- A10 verb re-homing: photondump body -> NetKit.ViewRegistryDump; waiver docs; V28-V31
- DonorKit: refuse a body-template capture with no AI graph
- Forge shell fixes: SSH commands via bash -c (fish login shell); set/cfgdump on every channel mod
- Forge shell: catalog dump + response protocol + set/cfgdump in ForgeKit; forge CLI/REPL + completion packs; wiki-enriched name db
- Merge branch 'release/pre-bundle-bodies'
- Bundle body tier: Pearlbird ships as an AssetBundle rig — expedition bypassed (B1/B2/B3a live-verified)
0.1.3 — 2026-08-11
- Phantom view-ID root cause + fixes: NetKit hb per-id attribution (top=[id×n]) + unknown-view RUNAWAY detector, DonorPhotonGuard duplicate-registration veto over live scene-baked views (DuplicateViewPolicy, unit-tested), V-PARKLEAK bounded-window acceptance + pt-stamped mute line; analysis doc + NK-PHANTOM1/2 retest rows — built, retest owed
- SpawnKit perf wave: hot-loop allocs removed, adaptive replica enforce, prune throttle, viewID watermark + opt-in corpse release, AI sleep radius, caps 8->12
- SA-0808 Wave C code half: spawn recovery — census, ghost fix-at-cause, quest-gate clear, TerrainManager guard, abandon quarantine
- SA-0808 Wave C data half: DonorScenes pin wave + sweep-doc corrections
- Static-analysis 2026-08-08 wave 0: nine P1/P2 fixes
- Review fixes: strip the whole VFXPlayed rebind family; pin only when it defends something
- BodyFx one-shot: a pin owns the pose, so a refused DestroyImmediate can't move the flourish
- 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
- DonorKit: fake-null sweep in the guards, rig forensics and expedition trace
- A1: the fake-null accessors, and every Plugin.cs cleared
0.1.2 — 2026-08-02
- DonorKit/SpawnKit: name the two silent paths
- DonorKit/SpawnKit: five static-analysis fixes
- DonorKit/SpawnKit cleanup: one rig gate, one Safe() reader
- Docs sweep: archive, condense, and validate the whole documentation tree
0.1.1 — 2026-07-30
- DonorKit gets a painterly butterfly net; Disengage now reads "return and stay"