SpawnKit
[ALPHA] Runtime enemy spawner for Outward: spawn any donor-scene creature beside the player as a real vanilla hostile, with full AI, aggro, combat, and vanilla death/loot. Co-op supported. Early software — see README for known issues.
CHANGELOG
SpawnKit changelog
0.5.2 — 2026-08-28
- Fix stale dependency pins across the fleet; DonorKit 0.1.7
- release prep: sync version numbers across all four files
- Cross-area pet orphan fix + three combat-staging diagnostics
- Merge feat/guest-voluntary-warm: guest self-warm opt-in + DR late-join re-push
- SpawnKit: guest voluntary warm — review fixes (M1/L1/M2/L2)
- SpawnKit 0.5.1: review nits — MintHeal.Step/SettleState shared by coroutine + replay test, max==base warning, startHp on the settle exit line
- SpawnKit + DR: guest voluntary warm (GM22–GM28, built, not verified)
- SpawnKit 0.5.1: co-op mint heal — refresh the max stat + settle watch (MP-HALFHP)
- F2-V2: raw-gate the PLAN half of the want duty, the forgiven re-flush and skresync
- F2-V1: stamp the guest warm routine with its room, so a stale one cannot free the latch
- AF2-13: additive LruView instead of retyping LruKeys (review CHANGES, ABI removal)
- AF3-2: gate SpawnKit's cross-peer host duties on RAW authority, not the flipped getter
- AF2-13: TemplateStore.LruKeys hands out a read-only view of the live LRU order
- AF2-9: cap the species count a warm row may decode, and refuse an over-long one
- AF2-8: say "sent this sweep" — the count is the whole want book, not my species
- AF2-7: the warm mirror stops mirroring its own row
- AF2-11: an abandoned template acquire still charges the donor cycles it burned
- AF2-10: reset the guest's harvest standing above OnRoomChanged's early-out
- AF2-5: the sk.want handler logs what actually happened, not "queued at head"
- AF2-3: an abandoned (actor, species) pair neither plans nor spends the per-peer cap
- … and 2 more (see git history)
aiLive=on the[SPAWN]mint line and everyspawndumprow — the one field that answers "can this body engage on its own?". Verdicts, most-global first:NoAiGraph,WorldPaused,AiBehaviourDisabled,NotStartInitialized,DistanceCulled,Ticking; anything butTickingprints its own remedy sentence. Classification is pure and unit-tested (core/SpawnKit.Core/AiLiveness.cs,tests/SpawnKit.Tests/AiLivenessTests.cs).charAIDisable=is documented at the call site as PRESENCE, not state. A headless session readcharAIDisable=Trueas "SpawnKit disabled the clone's AI" and concluded, for a whole stretch, that spawned wilds can never engage.CharAIDisableis a DISTANCE CULLER (CharAIDisable. AICloseToPlayerCheck) that only ever acts when no player sits insideDistanceToEnable; it says nothing about liveness. The field key and value are unchanged (grep contract) —aiLive=is the additive answer.- Why it matters: the decisive term is GLOBAL.
NetworkLevelLoader.IsGameplayPausedgatesAICEnemyDetection.Update,AISWander.UpdateandCharAIDisable.AICloseToPlayerCheckalike, so a wedged world-sim pause (which aloadsavecan land in, with every dev verb still answering normally) freezes VANILLA scene creatures exactly as hard as SpawnKit clones.aiLive=WorldPausednow names it and points atunstick fix. - Additive only —
COMPAT_SINCEstays0.5.0.
0.5.1 — 2026-08-24
- Co-op mint heal: spawns in a 2+ player room no longer arrive at half health.
ApplyCoopStatsthrows on a fresh clone before vanilla's ratio heal, and the host read a stale max stat; the mint heal now forcesRefreshVitalMaxStatfirst and runs a ≤1 s settle watch that carries the max delta onto current health (damage taken inside the window is kept). Host-only; solo path unchanged (SpawnKit.Core.MintHeal) - New
[SPAWN] mint-heal …/mint-heal settle …log lines; the mint header andspawndumpprinthp=cur/max base=B - Guest voluntary warm: a guest may Prewarm for itself (menu button /
spawnprewarm) under[Coop] GuestPrewarm; the ask rides the existing safe want queue (out of combat, loader idle) on its own per-room bucket (GuestPrewarmBudget) under a per-session ceiling (GuestDonorCycleCeiling);Core.GuestWarmPolicy+ class-taggedGuestHarvestStanding(built, not live-verified — GM22–GM28) - Published warm-row budget is now
min(mirror-left, ceiling-left)(WarmSetWire.PublishedBudget); the voluntary bucket stays local; no wire change skwarmdump/spawndumpprint both buckets, the session ceiling and the class-tagged queue- New public seam
Spawner.OnPeerSceneReady(master-only) — DangerousRoads re-pushes its room-warm list for late joiners
0.5.0 — 2026-08-22
- Room-wide warm mirror: host-side WantBook ledger, PeerOwned mirror of every peer's mintable species, host gate on party-wide mintability (Phase A observe + Phase B enforce)
- Wire-protocol bump:
sk helloannounces 0.5.0; the compare is exact-ordinal, so a mixed-build room refuses co-op loudly (same-bundle rule) - Version reconciliation: Plugin.cs already announced 0.5.0 since the warm-mirror wire bump; manifest.json / thunderstore.toml / csproj now agree
0.4.4 — 2026-08-19
- SpawnKit: casters attack — BUG-CASTERNOSKILL skill-knowledge repair (live-verified)
- Merge branch 'fix/sa-0815-mpnet' into feature/pet-self-feed
- Review fixes for the SA lane-F3 MP/net wave (H1, M1, M2, L1-L3)
- SpawnKit: review fixes — refusal memo must not shadow a later-valid template
- SA 2026-08-15 lane F3: MP/net teardown, mirror-race + hello-warn fixes
- SpawnKit: TemplateAiGate cluster fixes (SA-0815 F2b, A4-1/2/3/5/6/7/8 + A9-4)
- SpawnKit: refuse brainless templates — BUG-PREBUILTADOPT structural AI-graph gate
- SpawnKit: fix Character.Start never running on far-mint spawns
- spawn <TAB> completes the species roster — SpawnKit annotates spawn/spawnex/spawnprep; explicit choices win in the engine
- 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
0.4.3 — 2026-08-11
- Merge phantom view-ID work: root-cause writeup (donor-stray duplicate registration evicts scene-baked views via PUN's destroy-old resolution), DuplicateViewPolicy veto in DonorPhotonGuard, unknown-view runaway telemetry + hb top= attribution, corpse-mute window accepted as bounded (NK-PHANTOM1/2 + V-PARKLEAK A/B retests owed)
- 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
- Merge BUG-CONTROLLERCLOSE fix: controller B via vanilla ControlsInput.MenuCancelSystem() ORed into the same two-stage Esc gate (CI-safe, no Rewired_Core ref; retest owed)
- Merge Ghost root-scale fix (GV-SCALE1): donor's authored (0.02,1,0.02) root localScale rode Instantiate into every mint; RootScaleFix restores degenerate axes at mint time, SK_-uid + UseLegacyVisual=false scoped; retest GV-SCALE1b owed
- BUG-CONTROLLERCLOSE: controller B closes the SpawnKit menu via the same two-stage gate as Esc
- Invisible-Ghost fix: un-crush the donor's root localScale at mint (GV-SCALE1b)
- DangerousRoads relay whitelist gap + ghostdiag DEGENERATE threshold widen
- Fable-review fixes: viewID watermark reads outstanding ids (latched), corpse-release fallback never parks a neutralized view, FarCache InFlight can't leak, visual-pass retries count as busy, Notify header placement
- SpawnKit perf wave: hot-loop allocs removed, adaptive replica enforce, prune throttle, viewID watermark + opt-in corpse release, AI sleep radius, caps 8->12
- SpawnMenu search bar: pure SpeciesFilter (key + resolved donor name), per-frame latched row set, two-stage Esc
- Slope-tilt review wave: pure smoother + hysteresis deadband + per-species axis
- SA-0808 Wave C code half: spawn recovery — census, ghost fix-at-cause, quest-gate clear, TerrainManager guard, abandon quarantine
- Static-analysis 2026-08-08 wave 0: nine P1/P2 fixes
- Merge branch 'feat/pet-health-orb'
- Fix BUG-RAGDOLLJOINTLOSS: the rig re-init pass ate healthy ragdoll joints
- Pet health orb: replace the IMGUI bar with a green Outward-style vitals circle
- Fix BUG-GHOSTINERTSPAWN: re-run the init chain Character.Start already spent
- Merge Lane B: SpawnKit view lifecycle migrated into NetKit.ViewLease (phase 2)
- Phase-2 view-lease migration: SpawnKit's view lifecycle moves into NetKit
- Review fixes: strip the whole VFXPlayed rebind family; pin only when it defends something
- … and 8 more (see git history)
Unreleased
-
Refactor (behavior-preserving; built, NOT live-verified): the PhotonView LIFECYCLE (mint/bind/deferred release/age-out sweep/corpse mute) migrated into
NetKit.ViewLease(phase 2 of the view-hygiene migration).MintNormalizeandEnemySpawnerare thin delegations now; every[SPAWN]log line is byte-stable — though the lease-mechanics lines (deferred/released/aged-out) now carry theNetKitBepInEx source label and follow NetKit's[Diag] LogLevelrather than SpawnKit's (defaults identical; warnings un-gated on both) — and SpawnKit keeps its policy — the master-warn vs mirror-throw no-view asymmetry, the Vanilla-corpse may-persist declaration, and the corpse-worded once-per-entry aged-out notices (supplied to the lease as callbacks). The parked-release ledger + the 300s age-out rule gained their first unit tests (NetKit.Tests/ViewLeaseTests). Template normalization deliberately keeps its bareviewID=0(a dormant template must stay mintable — see the new warning note inSpawnTemplates.Normalize). -
DangerousRoads 0.1.1 + SpawnKit 0.4.2: re-pin off CompanionKit 0.4.2
-
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.4.1 — 2026-08-02
- Hyena/Pearlbird tuning wave: HAO taunt, gifts, bone relic, feed rule