CeruleanCutlass-SpawnKit icon

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 every spawndump row — the one field that answers "can this body engage on its own?". Verdicts, most-global first: NoAiGraph, WorldPaused, AiBehaviourDisabled, NotStartInitialized, DistanceCulled, Ticking; anything but Ticking prints 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 read charAIDisable=True as "SpawnKit disabled the clone's AI" and concluded, for a whole stretch, that spawned wilds can never engage. CharAIDisable is a DISTANCE CULLER (CharAIDisable. AICloseToPlayerCheck) that only ever acts when no player sits inside DistanceToEnable; 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.IsGameplayPaused gates AICEnemyDetection.Update, AISWander.Update and CharAIDisable.AICloseToPlayerCheck alike, so a wedged world-sim pause (which a loadsave can land in, with every dev verb still answering normally) freezes VANILLA scene creatures exactly as hard as SpawnKit clones. aiLive=WorldPaused now names it and points at unstick fix.
  • Additive only — COMPAT_SINCE stays 0.5.0.

0.5.1 — 2026-08-24

  • Co-op mint heal: spawns in a 2+ player room no longer arrive at half health. ApplyCoopStats throws on a fresh clone before vanilla's ratio heal, and the host read a stale max stat; the mint heal now forces RefreshVitalMaxStat first 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 and spawndump print hp=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-tagged GuestHarvestStanding (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 / spawndump print 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 hello announces 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). MintNormalize and EnemySpawner are thin delegations now; every [SPAWN] log line is byte-stable — though the lease-mechanics lines (deferred/released/aged-out) now carry the NetKit BepInEx source label and follow NetKit's [Diag] LogLevel rather 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 bare viewID=0 (a dormant template must stay mintable — see the new warning note in SpawnTemplates.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