Outward
Install with App

Details

Latest version
0.6.2
Last Updated
First Uploaded
Downloads
849
Likes
2
Size
320KB
Dependants

SpawnKit changelog

0.6.2 — 2026-09-05

  • Static-analysis wave 5: release prep for Beastwhispering 0.2.18 + DangerousRoads 0.1.13
  • SpawnKit: materialize a clone's unspent StartingPouchItems weapons at mint (Case B upstream, measured)
  • SpawnKit: MeleeSidearm review fixes — right-hand-only, verified swaps, lifetime cap, kill switch
  • SpawnKit: MeleeSidearm — a bow-holder stuck in the melee AI state gets its sidearm forced
  • SpawnKit: restamp WeaponType after equip and every animator Rebind
  • ForgeKit 0.4.13 / DonorKit 0.1.9 / SpawnKit 0.6.1: session-3 fixes
  • Merge branch 'worktree-agent-ad0ca166327fb8804' into integrate/session3-fixes
  • Merge branch 'worktree-agent-a92939b0a3b7c1d30' into integrate/session3-fixes
  • Fix release-blocking version skew: NetKit 0.2.10, Beastwhispering 0.2.17
  • SpawnKit: N2 review fixes — mint gates need both authority terms
  • DonorKit/ForgeKit: PRE-1/PRE-2 harvest resource census + liveness heartbeat
  • SpawnKit: N2 authority reads use raw room-host, not flipped master
  • SpawnKit 0.6.0 / DangerousRoads 0.1.10: pre-session follow-up close-out (B1 knobs, quiet-roads visibility, batched warm asks, mode-faithful draw condition)
  • Merge branch 'main' into worktree-agent-a5a0898794ba1be15
  • SpawnKit+docs: PR3-mode review nits — single mode read in CanEventuallyShowRoomWide; PR3-5 FAIL wording excludes the draw-to-spawn race
  • SpawnKit: B1-nopark SpawnOptions.NeverFarPark, the event-owned mark
  • SpawnKit+DangerousRoads: PR3-mode draw-time predicate mirrors the spawn gate per RoomWarmMode (dead-end sits out at defaults)
  • SpawnKit 0.5.5: MP-hardening sweep close-out (L3b, B5, B8, B12, PR1, PR5, L3c, C2, C1)
  • Merge branch 'main' into worktree-agent-a54a97473c5bb76f6
  • SpawnKit: PR5 swap leg — the placeholder hot-swaps to the real body, strictly serialized
  • … and 23 more (see git history)

0.6.2 — 2026-09-04 (live-tested on legion)

Two live-found P0s off the 2026-09-04 session.

  • WeaponStamp: spawned armed humanoids swing again. Every SpawnKit-spawned armed humanoid played the unarmed punch clip and dealt zero damage — Cobalt, live: "the bandit was doing the punch animation with the axe drawn and he did no damage to my pet", across multiple species. Outward has no animator override controllers: one humanoid controller picks punch-vs-weapon clip sets from a single animator int WeaponType, written ONLY by Character.WeaponChanged(), which is triple-guarded and silently no-ops on guard failure with no retry. Weapon damage rides HitStarted animation events on the ARMED clips, and the unarmed fist detector is assigned only when CurrentWeapon == null — so weapon-bound + unarmed-clip means the blade sweep never arms AND the fist path is locked out. One cause, both symptoms, and every game-state field read correct because WeaponType is not game state. grep WeaponChanged src/SpawnKit was zero hits while SpawnKit called Animator.Rebind() (which resets every parameter) in three places after equipping. We had already fixed this once — GhostPeer's Ghost.Restamp (GH19, same verbatim symptom); the knowledge never crossed kits. Now re-stamped after the equip and after every Rebind, with a deferral queue so an early stamp waits for the animator instead of being discarded. Boot assertion proves both DonorKit guards' prefixes actually attached — a mismatched Harmony patch neither applies nor errors. Live: punch clips 13 -> 0, armed attack clips 0 -> 13, beasts unregressed.
  • MeleeSidearm: a bow-holder stuck in the melee AI state. AISCombatMelee.UpdateAttackCheck and TryStartAttackPattern are both gated on CurrentWeapon is MeleeWeapon; a ProjectileWeapon is not one, so a bound bow in the melee state is frozen forever. Positioning, facing and dodge carry no weapon-class gate — which is exactly why three observed raiders evaded while never swinging, with the merchant's HP flat for minutes. Now detected and swapped to a right-hand melee sidearm, keyed on the observable end state rather than on why the bow is bound (the upstream — a failed EquipRequiredWeapons swap on a clone — remains inferred and is instrumented, not assumed). Post-equip verification before an attempt is spent, a sticky lifetime cap so an equip tug-of-war cannot recur per episode, vanilla's own InLocomotion && !IsCasting guard, and inactive candidates skipped (a bag deactivated by the ItemUidGuard leaves inactive weapons visible to the scan).
  • MirrorAuthority (N2): a SplitScene authority flip made GuestReplicas read the getter SplitScene postfixes and silently clear the host's warm wants; the host then retired the species for the session. Authority reads now use the raw room-host — EXCEPT the two mint gates, which require the conjunction (raw says guest AND vanilla will convert the replica), because raw alone there would mint a live AI copy of the master's enemy on its own viewID.
  • New: [Spawner] MeleeSidearmFix (default true, live) gates both the sidearm poll and the mint-time pouch re-registration. spawndump gains wpnType= offHandType= wpnClass= pouchWpn= pouchReg= sidearm= swapState= tries= lifeswaps=wpnType is the field that had never been read and which root-caused the punch bug.
  • Retests: spawnkit-testplan.md (BUG-HUMANOIDPUNCH PASS, CB1-CB8), guest-mirror-harvest-testplan.md.

0.6.0 — 2026-08-30 (built, not live-verified)

The pre-session follow-up wave: three additive surface pieces (hence the MINOR bump).

  • SpawnOptions.NeverFarPark, surfaced on the handle (public setter — ownership is a phase; the owning event releases the mark in Teardown and on the torn-down early-outs): an event can mark a creature it owns so a consumer's own distance-based park pass leaves it alone. SpawnKit does not park anything itself — it carries the flag for consumers that do (DangerousRoads sets it on patrol troglodytes and stranger mobs; a parked member counted as neither alive nor dead, which is how a patrol could report alive=0 dead=0). Retest: dr-farcache-testplan.md FC15.
  • Spawner.CanEventuallyShowRoomWide(key) + pure SpeciesRoomPolicy.CanEventuallyShow: the draw-time question ("could a spawn ever get past the spawn gate under the operator's RoomWarmMode?") as distinct from CanMintRoomWide's "cleanly, now". Under the default RoomDegraded it refuses only a TERMINAL dead end — exactly what the B12 spawn gate refuses — so a card can no longer be drawn eligible and then refused at spawn. Retest: dangerous-roads-testplan.md PR3-5.
  • Batched warm asks bind RoomWarmRequestCap once. The cap is per-peer PER CALL, and DR's old per-key loops multiplied it (11 asks past a cap of 3); DR now sends one list per refusal/tick. The truncated tail moves up once the head resolves on that peer. Retest: guest-mirror-harvest-testplan.md PR2-BATCH1.

0.5.5 — 2026-08-30 (built, not live-verified)

The co-op guest-mirror half of the MP-hardening sweep: degrade, never abort. A guest that cannot yet show a species gets something harmless to stand in for it and a route back to the real body, instead of a refusal that either wedges or deletes.

  • The destroyed-cache re-harvest is capped, and the mint says where the body really came from (L3b). The guest mirror's donor-chain cap was passed as wasCached ? 0 : cap, and 0 means UNCAPPED in DonorHarvest — so precisely the case that needed the cap most (a cached template destroyed under us, forcing a re-harvest) ran without one. The cap now always applies, and the src= label on the mint is decided from the donor cycles actually spent: a re-harvest reports the new cache-lost-reharvest instead of falsely claiming src=cache. Policy in Core.MirrorAcquirePolicy; row GM29.
  • An sk.gone inside the mint dwell no longer fires a bogus sk.fail(mint-inactive) (B5). The 2-frame dwell that lets a freshly minted replica settle would report the mint inactive when the master had simply retired the spawn mid-dwell — the very reason this machine had removed the row itself. The resume now checks the registry row first and ends silently in that case (Core.MintResumePolicy). The remaining inactive-within-dwell warnings (GuestReplicas and both EnemySpawner siblings) now state what was actually observed and offer the duplicate-UID guard as a checkable hypothesis rather than a verdict. Row GM30. Audited alongside it: SpawnNet.OnFail carries no retry or blacklist keyed on failure reason, so the field harm here was log noise and mirror-ledger pollution, never a double-despawn.
  • A kept cold-unsafe spawn records its re-flush obligation (B8). SpawnNet.OnFail now writes the Forgiven-ledger re-flush obligation for EVERY cold-unsafe report that leaves the spawn Alive — degraded and forced spawns included — paired with a RequestRoomWarm. RoomGateOk no longer gates the plumbing, only the forgiveness verdict. The despawn decision is untouched by this change. Row GM31.
  • The warm-request deadlock: terminal dead ends refuse, and guests replay their own refused spawns (B12). RoomDegraded no longer spawns past a TERMINAL DeadEndOnPeer — the degraded branch reads warm.Reason through SpeciesRoomPolicy.IsDeadEnd (the verdict is downgraded by design, so the reason is what still carries the truth) and refuses both at preflight and at the broadcast-time re-check. And the new bounded per-uid Core.ColdPendingBook (16 rows) keeps a cold refusal's (uid, species, wire message), so the species' warm completion replays the guest's OWN spawns into the queue with no dependence on the host's Forgiven ledger. A gone forgets its pending uid, a room change clears the book, and IsKnown counts pending rows so the host's re-flush dedups against them. Rows GM32/GM33.
  • cold-busy — a refusal that means "not right now", not "never" (PR1). New benign wire reason covering RefuseCombat + RefuseBudget + RefuseCeiling (Core.MirrorFailPolicy.ColdRefusalReason, unit-pinned); RefuseAdmission and anything unknown stay cold-unsafe and destructive. The master's benign leg records the Forgiven obligation and requests the warm for a cold-busy report. In ShouldDespawnColdUnsafe the !roomGateOk clause is GONE: it is now one grace per (uid, actor) for every spawn, degraded included — degraded is precisely the mode that expects cold guests. The 27-line Phase-B rationale beside it was rewritten in the same change to carry that ratified override and to say why once-only still stands; the roomGateOk parameter is retained for the log line and call-site compatibility. Rows GM34/GM35.
  • Placeholder bodies: a cold refusal mints a stand-in bound to the leased viewID (PR5, degrade leg). New [Coop] PlaceholderBodies (default true). Instead of nothing standing where the master says a creature is, the guest mints a CompanionKit ghost stand-in on the leased viewID (Views.Neutralizepv.viewID = m.ViewId, group restored to 0 so it receives), with the AI stripped inside the INACTIVE window — EditorCharacterAILoadAI destroyed before IsAI's first latching read — a SKPH_ UID (so SK_ stays free for the swap), the master's faction stamped after activation (ChangeFaction sync:false), and sk.ack(Source="placeholder"). It is harmless by construction: CensusAndEnforce early-returns on Placeholder rows so EnsureEquipped never fires, and it is NOT Invincible. Requires CompanionKit 0.4.29 (the GhostStandIn access widening). Row GM36.
  • …and it hot-swaps to the real body, strictly serialized (PR5, swap leg). Core.PlaceholderSwap is the pure sequencer (Standing / Swapping / Retired, with a DeferGone latch where Removed outranks Died; the exhaustive table and every length-4 sequence are pinned in PlaceholderSwapTests). The executor GuestReplicas.SwapRoutine is yield-free and its order is strict — identity-checked deregister → Views.Neutralize → destroy the placeholder → Mint(template, m, "swap") → row takeover — because a same-UID newcomer deactivates its predecessor and IsAI latches on first read. The fresh row keeps Placeholder=true through its 2-frame dwell so a mid-dwell gone routes through the machine; completion drops the flag, acks Source="swap" and runs the normal census/visual tail. A template evicted before the destroy rewinds to Standing and re-wants the species, so the swap is retriable. Row GM37.
  • The cold-busy and keep-configured deferrals are BOUNDED. Worth saying out loud, because it deliberately overrides an operator setting: the Forgiven expiry window now bounds — and after ~60 s despawns — spawns kept under DespawnOnMirrorFailure=false as well as cold-busy deferrals whose warm never lands. The ledger backstop wins over "keep" on purpose, to bound the B10 ghost/scan hazard. Related, under DespawnOnMirrorFailure=false plus LRU thrash (a non-default combination): a warm→evict→re-flush→refuse ping-pong can cycle for as long as the peer's row keeps genuinely changing. It is paced by real warm completions rather than by a timer; noted beside B11's cache-coupling doc.
  • Guest scene-batch harvest: k species out of ONE additive load (L3c — the resolution of B12's budget deadlock). A master-instructed warm on a guest now carries every other wanted-cold species that shares a donor scene with the one being warmed, charged as ONE donor cycle: the LightProbes crash resource degrades per load/unload, not per species, and billing needed no change because WarmRoutine already charges DonorHarvest.CyclesThisSession deltas. The DangerousRoads patrol roster (~11 species across ~3 scene families) now fits the shipped MirrorHarvestBudget=4, unit-pinned. Batch bound (the B11 coupling): companions per batch = min(wanted, free template-cache slots after the primary, SceneBatchPlan.DefaultMaxBatchSpecies = 12 including the primary) — never build a template the LRU evicts in the same Insert. The serial warm latch, the safety re-checks and per-species failure isolation are all unchanged, and a companion that is not live in the settled scene keeps its own queue entry and chain. New pure policy core/SpawnKit.Core/SceneBatchPlan.cs + SceneBatchPlanTests. Rows L3C1L3C3.
  • ADDITIVE: SpawnTemplates.AcquireScene gains an allowNonMaster overload mirroring Acquire's, honored for master-instructed warms and carrying the same attribution log; the 2-argument form is unchanged. COMPAT_SINCE stays 0.5.0.
  • skwarmdump tells a quiet peer from a frozen one (C2). WarmMirror tracks a HOST-LOCAL lastHeardAt per actor, updated on ANY message received whether or not it decodes, using Time.unscaledTime on this machine — never a wire field. PeerRow.At only advances on a message we could actually READ, so a peer sending garbled bytes every tick used to read as "frozen" by age alone. The dump now prints landed= beside heard= for Participating peers and a last-heard age for Unmodded / HelloedNoRow rows too; the table is cleared on a room change, because actor ids restart and a stale timestamp would name a stranger. Row C2-LASTHEARD in docs/guest-mirror-harvest-testplan.md.
  • Comment-only (C1): the warm-set heartbeat's loss-tolerance note now names the real reason it exists — peer-state transitions (late join, resync, room change, a peer not yet scene-ready), not transport loss. PUN RPCs are reliable, ordered and deduped; loss means disconnection. The heartbeat stays.
  • Comment-only (L3c review follow-up): the batch trigger comment states the true condition — mirror mints batch too, not only the harvest path.
  • Dependency pins raised to the sweep floors (CompanionKit 0.4.29, DonorKit 0.1.8, NetKit 0.2.8).

0.5.4 — 2026-08-29 (built, not live-verified)

  • Preflight refuses while the world is not liveFailReason.GameplayNotLive (14, appended; COMPAT_SINCE stays 0.5.0). Reads ForgeKit.Lifecycle.IsGameplayLive(): a level load in flight OR the loader still holding gameplay paused after it (the "press any key" window), where a spawned body sits in CharacterAI.Update's paused branch and comes up T-posed with an inactive AIStatesRoot (education/who-poses-the-body.md #8). Distinct from NoPlayer — the player exists in that window. DescribeAiLiveness's own paused readback is unchanged (it is a readback on an existing body, not a gate).

0.5.3 — 2026-08-29

  • postrace <name|uid:xxx> [seconds=20] [hz=5] dev verb (PosTrace.cs): the BUG-SPAWNLAUNCH discriminator. Coroutine-sampled [POSTRACE] line per tick on one tracked spawn — y, grounded=<cc.isGrounded>, falling=<m_falling>, totem=<m_charTotemMove>, agent=<on|off>/onMesh/next.y, ragdoll, clip, and overlaps= every collider inside its CharacterController (self excluded, layer named). Stops on despawn/death/timeout or postrace off. Requires CompanionKit 0.4.23 (pin only).

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
Thunderstore development is made possible with ads. Please consider making an exception to your adblock.