StoryKit
Add an NPC to Outward as plain data: StoryKit builds the character, spawns it at a fixed spot, wires its dialogue, and compiles a trainer skill tree that sells from the vanilla Trainer UI. Requires SideLoader at runtime. A library dependency.StoryKit changelog
0.1.12 — 2026-09-05
- StoryKit 0.1.12: complete the partial bump (manifest/thunderstore/csproj + consumer pins)
- Merge branch 'worktree-agent-ace7c8e6b563bfb04'
- ForgeKit 0.4.13 / DonorKit 0.1.9 / SpawnKit 0.6.1: session-3 fixes
- Fix release-blocking version skew: NetKit 0.2.10, Beastwhispering 0.2.17
- StoryKit 0.1.12: NpcDialogueHold — stop-and-face during dialogue for every spawned NPC
- Echoes/StoryKit/GhostPeer: talk-to-wraith set-free SPIKE (Lane 6/7) — additive, OFF by default
- GhostPeer: import saved enchantments — a hex-blade stays a hex-blade
- GhostPeer VP7: full-character import (stats/skills/quickslot policy from the save) + post-rest weapon re-stamp
- DangerousRoads 0.1.11 / StoryKit 0.1.11: bug-swarm close-out (BUG-TROGVSTROG, FC13, SK-D1-NRE, STALEBUSY)
- StoryKit: STALEBUSY follow-up — clear a proven-stale LOCAL Buyer latch (never a remote one)
- StoryKit: STALEBUSY review fixes — a checkout in flight is hard-busy; correct the host-latch claim
- Merge branch 'main' into worktree-agent-ae7f08be6931734b3
- StoryKit/DangerousRoads: STALEBUSY Merchant.Buyer is a latch — corroborate it, and name the busy leg
- StoryKit: SK-D1-NRE heal CharacterAI's null quest-event ref before disabling the AI
- StoryKit 0.1.10: the StoryKit→NetKit edge is ratified and landed (BepInDependency + KitContract.Declare + DependencyDirectionTests; Echoes gains the direct NetKit refs the R2 dist-dup rule demands)
- StoryKit 0.1.9: MP-hardening sweep close-out (SK-D1/D2/D3/D4/D5/D6/D12/D13, SK-D14 v2)
- StoryKit: SK-D13 review follow-up — forcing Unregister overload; wire SK-D8's Overdue ceiling and the DR teardowns through it
- Merge branch 'main' into worktree-agent-a91f6de81384aa9e3
- StoryKit: SK-D6 master migration detected by role, not room name — reset, abandon in-flight, sweep the departed host's minted orphans
- StoryKit: SK-D13 despawn guard moves inside the registry — refuse (never defer) while the NPC is in dialogue
- … and 23 more (see git history)
0.1.12 — 2026-09-04 (built, not live-verified)
- Every StoryKit dialogue NPC now stops moving and faces the player for the duration of a
conversation (
NpcDialogueHold). Live bug (Cobalt 2026-09-04): a wandering Echoes echo ("LaughingJim") kept walking away while being talked to. Baked in at the rig level — a Harmony guard onAISWander.Update(the same source-suppression pattern asTorsoLookGuard) that, while the body's dialogue/shop is running (NpcRegistry.IsBodyInDialogue, the single busy-read source of truth), halts the wander at its source (CharacterAI.StopMovement+SpeedModif=0, so no per-frame re-write or re-path drift) and turns the whole body (not just the head) to face the nearest local player. The freeze is a per-frame suppression: nothing on the AISWander route is mutated, so the exact borrowed patrol resumes on its own the tick the conversation ends — no stored state, no coroutine. Opt-out via the new additiveNpcSpec.HoldStillInDialogue(defaulttrue); echoes/merchants/trainers/mobile NPCs inherit it with zero consumer code. Decidable bits (ShouldHold,TryFaceYaw,ShortestTurn) are pureStoryKit.Core.DialogueHold, unit-tested. Live retest:echoes-testplan.mdEC-HOLD1.
0.1.11 — 2026-08-30 (built, not live-verified)
The bug swarm off the 2026-08-30 live session.
- Despawns no longer leave a swallowed
CharacterAI.OnDisableNRE (SK-D1-NRE). Root cause found in the decompile:m_aiActiveOnQuestEventis a serializable class Unity only allocates during scene deserialization, so every SideLoader-built body carries null and vanilla derefs it unguarded on BOTH enable and disable.NpcRegistrynow heals a null field with an empty reference at rig time (and before pre-disables for unrigged orphans) — never overwriting a real quest gate. Side effect: vanilla's own distance-culling NREs on our bodies stop too. Retest:storykit-mp-testplan.mdSK-D1-NRE. - The shop-busy read is self-limiting and transaction-aware (STALEBUSY — the live session saw
despawn retries read HELD after the shop closed). Vanilla's
Merchant.Buyeris a durable latch with exactly one fragile clearing path (the shopper's own ShopMenu.OnHide → QuitShop → exit RPC — which silently no-ops on a missing pouch; disconnect/death/scene-change/save-restore all leave it set). The merchant busy leg now corroborates a local shopper with the actual Shop panel (3s open grace), trusts a remote shopper for 300s then expires, and treats an in-flight checkout (IsTransactioPending) as HARD busy ahead of everything — money mid-move can never lose its merchant. A PROVEN-stale local latch is cleared (fixing vanilla's merchant-unshoppable-for-the- session wedge and unfreezing the greet-pinned walk); the remote leg is never cleared (clearing under a genuinely-shopping guest trades one latch for a worse one). Every refusal/HELD line now names WHICH leg read busy. Guest caveat: a guest browsing continuously past 300s may have the merchant walk off (T3-mp). Retests:road-merchant-testplan.mdSK-D8b/c/d.
0.1.10 — 2026-08-30 (built, not live-verified)
- The StoryKit → NetKit dependency edge is RATIFIED and landed (Cobalt, 2026-08-30;
docs/storykit-dialogue-wire-plan.mdopen question 1).[BepInDependency(NetKit)]+KitContract.Declare+ theDependencyDirectionTestsallowance, in one change. This release carries the EDGE only — no wire code yet: thestorychannel, thedlgrevision store and thestory.actrequest/ack are a later sweep's work, per the plan doc. No shipped profile gains a DLL (every profile already carries NetKit via CompanionKit/SpawnKit); the bundle-closure computation just starts saying so honestly.
The multiplayer half of the MP-hardening sweep. Every finding below is BUILT and unit-green; none of it
has been in front of a live two-box session. Rows: docs/storykit-mp-testplan.md SKMP1–SKMP6 and
SK-D12a–SK-D12d/SK-D5b, docs/road-merchant-testplan.md RM26.
NpcRegistry.Despawnno longer reports a success it did not achieve (SK-D1). SideLoader's destroy always failed — a SetActive-first NRE inCharacterAI.OnDisable, swallowed by PUN — and the UID leaked forever behind a normal-looking log line. The AI is now disabled before the destroy, the destroy is wrapped, and a ~0.3 s verification pass logsdespawnedonly once CharacterManager has actually forgotten the UID; when SideLoader's coroutine died it force-cleans (manager removal plus a direct destroy, mirroringDelayedCharacterDestroy). The bool keeps its signature, and its XML doc now says what it has always meant: "destroy issued", never "the world changed". Rules inStoryKit.Core.DespawnRules.- Every mutating registry op is master-gated (SK-D3).
Despawn,Unregister,Respawn,RebuildandReplaceGreetingsrefuse with a named line —[STORYKIT] <Op>('<id>') REFUSED — non-master client …. One guest call used to delete an NPC permanently on every peer (SideLoader's destroy RPCs to all) and log as if it had worked.Rebuild/ReplaceGreetingsare gated rather than documented as local-cosmetic, because a guest-local rebuild forks guest spec state;Rebuildalso no longer mutatese.Specbefore its liveness read. TheSpawnAtAndGetguest gate is hoisted to the top, so a guestSpawnAtrefuses synchronously instead of returning true and deferring. Authority rules inStoryKit.Core.RegistryAuthority. - The despawn-vs-dialogue guard moved INSIDE the registry (SK-D13).
Despawn/Unregister/Respawnrefuse with[STORYKIT] <Op>('<id>') REFUSED — in dialogue/trading …whileIsInDialogue(id)reads busy, instead of destroying a Character under a running DialogueTree (the merchant-greet bug, previously guarded caller-side only and blind to guests). The semantics are REFUSE, not defer: the bool stays honest after SK-D1, and it composes with caller-side deferrals like DangerousRoads'ShopDeferbecause every retry takes a fresh read, so a double-defer cannot wedge.Unregisterrefuses the whole op while busy rather than dropping the entry around a live body. Verdicts inStoryKit.Core.DespawnBusyRules. Known limitation, recorded on the row: a guest's conversation with a DangerousRoads-minted (key-less) spec still reads not-busy (AO4-1). - ADDITIVE: forcing overloads
Despawn(id, evenIfBusy)andUnregister(id, evenIfBusy)for teardown that must win. They exist because SK-D13's refusal broke SK-D8's composition: RoadWalker's Overdue ceiling firedEnd()while busy andMerchantCard.ClearLivetore its handles down around a REFUSEDUnregister, leaving a live merchant, a live entry and a live template with nobody left to retry. The docs are explicit that a non-forcing caller must KEEP its handle on a false.COMPAT_SINCEunchanged at 0.1.5. - A Photon master migration is no longer a no-op (SK-D6).
NpcDirectorwatchesisMasterClientalongside the room name (StoryKit.Core.RoomRoleWatch; a simultaneous room change subsumes the flip). On a flip: a named log line,ForgetLive(),_specFailures.Reset()(SK-D4's hand-off), an immediate converge, andNpcRegistry.AbandonInFlight()— an epoch counter that makes in-flightSpawnWhenLive/RespawnWhenClearcoroutines abandon themselves by name, also applied on a room change. A PROMOTED master sweeps the departed host's minted orphans (SL-named, no local template, no local spec, plusOrphanSweepRules.LooksMintedSpecId— deliberately conservative against SK-D11's ~95% false-positive recognizer) through SK-D1's verified destroy; a mid-dialogue orphan is left standing with its own line. The inverse guard: a peer demoted inside the SK-D1 verify window logsNOT force-cleaningrather than force-cleaning locally. Static specs still self-heal viaFirstMissingInafterForgetLive. Core:MigrationRules.cs. - One throwing spec no longer takes every later spec down with it (SK-D4). Both director walks —
master placement and guest rig — isolate each spec in its own try/catch, so a throw no longer skips the
rest, no longer silences the AO4-1 unmatched-template sweep, and no longer retries forever. Failures
are counted per id and the director gives up on a spec BY NAME after 5 (new pure
StoryKit.Core.RigFailurePolicy). A hard cap rather than a timed backoff, deliberately: the 3 s convergence tick is already the clock, and a second clock is a second thing to reason about in a log. The ladder is failure 1 = warning with the full exception, 2–4 = one line with the count, 5 = an error naming the id and saying attempts stop. Three re-arms: a success clears the id, a room change resets, and a SCENE change resets — the last is what makes the guarantee true in single-player, wherePhotonNetwork.roomis null all session and the room watch never fires again. - A guest no longer rigs replicated NPCs inside the loader's paused window (SK-D5). The
IsGameplayResumed()gate sits above the master/guest branch inConverge, so guests stop producing naked / T-posed bodies with an inactiveAIStatesRoot. And the "already rigged" marker is now honest:DialogueBuilderwritesNpcLookFollowat the END of a successful rig rather than at the top, so a throw further down no longer leaves a body marked-but-unrigged and never revisited.DialogueBuilder.BuildRETHROWS after installing its fallback graph, which makes a failed dialogue build a counted terminal state that escalates to SK-D4's give-up line instead of a silent re-rig every 3 s. The director also re-attempts a spec whose rig has thrown despite the marker, logging(retry #N after a failed rig)— harmless once the marker move is in, and the belt to its braces. - StoryKit NPCs enter vanilla's multiplayer dialogue lock (SK-D12). Every NPC now gets a
DialogueActorLocalizecarrying its spec id asActorLocKey, which is whatSceneInteractionManagerrequires before it will arbitrate a conversation through the master client. The key was always empty, so dialogue started local-only on every peer: two players could open the same NPC at once and the host could not see that a guest was talking —NpcRegistry.IsInDialogue's MP branch was dead code, which is also what made SK-D13's guard toothless for guests. A localization row is registered under the key first, so the NPC is still shown by name rather than by its id, and the rig-census line now carriesactorKey=. Rules inStoryKit.Core.DialogueLockRules. Two known limits, documented as rows and neither fixed: one benign Unity-swallowedDialogueActorLocalize.AwakeNRE per rig (Awake runs insideAddComponent, beforeLocKeycan be assigned), and an in-session language change renaming every StoryKit NPC to its raw spec id until the next rig (the loc table is cleared on reload and there is no re-registration hook). - The merchant's fallback stock stops refilling on demand (SK-D2). The fallback item list now rides
the same 72-game-hour refresh clock as a real stock roll (
MerchantPouch.m_nextRefreshTime), so buying a shop out and reopening it mints nothing until the clock comes round. Every reopen of an emptied shop used to regenerate the whole fallback list throughGenerateItemNetwork— an unbounded item fountain, in solo as much as in co-op. New pure ruleStoryKit.Core.MerchantRefreshRules; one entry pointMerchantWiring.RefreshAndMaybeFillFallbackreplaces the two ad-hoc refresh-then-fill call sites. RowRM26. - ADDITIVE, Core only, nothing wired:
StoryKit.Core.DialogueWire— the pure codecs and state for the planned guest-dialogue wire (channel/verb constants, epoch+revision stamps with a migration-safe compare ledger, the action-invoke request body, and an ok/refused ack grammar with named refusal reasons), unit-tested. No channel is registered and no dependency edge landed: the StoryKit→NetKit edge is an architectural decision for Cobalt, and the case is written up indocs/storykit-dialogue-wire-plan.md(SK-D14 v2, withDW1–DW10draft verify rows). - Comment fix (SK-D12): the Awake-NRE rationale in the file was wrong; corrected in place, with the two known limits recorded beside it.
- Dependency pins raised to the sweep floors.
0.1.8 — 2026-08-30 (built, not live-verified)
RingSpawner.SpawnAround— collision-safe ring spawn (additive;COMPAT_SINCEunchanged). Puts registered spec ids on a ring around a centre, slot 0 atcentreAngleDegclockwise from a world heading (180 + the player's forward = behind the player), fanned acrossspreadDeg. Every slot is navmesh-snapped (Walkable area, 2.5 m), rejected on a non-trigger collider at body height (Physics.CheckCapsuler=0.4, 0.3–1.8 m) or when withinminSpacingof a placed body, thenRingPlan.Fallbacks(±15°, ±30°, then radius ×0.75 / ×1.25) are tried in order; a spec with no surviving candidate is Refused with the reason. Spawns viaSpawnAtAndGet(…, SpawnPolicy.Refuse)so the gameplay-live gate applies. Pure plannerStoryKit.Core.RingPlan(RingPlanTests). Log tag[RING].
0.1.7 — 2026-08-29 (built, not live-verified)
- The spawn gate, in the registry (additive;
COMPAT_SINCEunchanged at 0.1.5).SpawnAt/SpawnAtAndGet/TrySpawnnever spawn whileForgeKit.Lifecycle.IsGameplayLive()is false — a body born in the loader's paused window comes up T-posed / naked / with an inactiveAIStatesRoot(education/who-poses-the-body.md#8). NewNpcRegistry.SpawnPolicy { Refuse, Defer }+ overloads:SpawnAt/TrySpawndefault to Defer (spawns when the gate opens, ≤30 s then anyway, abandoned on a scene change; returns true = live now or accepted);SpawnAtAndGetdefaults to Refuse (null + one[STORYKIT] '<id>': spawn REFUSED — gameplay is not live (<which half>)line) because its callers (the DangerousRoads cards, Echoes) need a synchronous body.NpcDirector.IsGameplayResumednow IS the kit predicate. - Posture census replaces the Bug-46
RepairVisualsWatch: one[STORYKIT] posture '<id>' @3s: renderers= visualsInit= animator= init= ctrl= aiRoot= closeToPlayer= spawnAnimDone= live=line per spawned body. Repairs in place: bare →InitDefaultVisuals+Rebind(as before); animator unbound / disabled / no controller → enable+Rebind;AIStatesRootinactive → named, and re-activated only when vanilla's own rule (CharacterAI.cs:523-531: live, spawn anim done, alive, not quest-gated) would have. Echoes' private copy of both the gate and the census is gone. AiSpec.WanderSpeeddefault 1.1 →NpcSpec.WalkSpeed(0.3), withNpcSpec.RunSpeed(1.1) as the other word. SideLoader's 1.1 is a full run; every "sprinting NPC" sighting was this default. The test pedlar saysRunSpeedexplicitly; the stranger card saysWalkSpeed. Consumers that set a config value (DR merchant/patrol) are unchanged.
0.1.6 — 2026-08-29
- Saved characters (
0.1.5→0.1.6, additive;COMPAT_SINCEunchanged at0.1.5). Lifted from Echoes for its second consumer (DangerousRoads' familiar-stranger card):StoryKit.Saves.SaveScan/SavedCharacterRecord— read every other saved character off disk (newest readable snapshot, vanillaCharacterSave.LoadFromFile; null-vs-empty contract kept).SavedCharacterSpec.FromRecord(rec, id, opts)— a look-alikeNpcSpec: visual indices verbatim, worn gear resolved to SL slots via the prefab'sEquipSlot.SaveHandoff.Mint/OfferPick/RemoveFromSave— saved entry → liveItem; the one-take container-panel pick (Harmony postfix onItemContainer.RemoveItem, filtered to the offer container); permanent write-back as a NEW snapshot folder. Log tag[SAVEHANDOFF].NpcSpec.Visuals(VisualIndices, wins overRandomVisuals),NpcSpec.BackpackId,NpcSpec.ShieldId.- Core:
SavedItemParser(parser + hierarchy classifiers +EnchantmentIds),SaveSnapshotRules.
- Echoes 0.1.1 now depends on StoryKit and registers its echoes through
NpcRegistry(Echoes.Core.EchoRuleskeeps its surface as forwarders overStoryKit.Core.SavedItemParser).
0.1.5 — 2026-08-28
- Fix stale dependency pins across the fleet; DonorKit 0.1.7
- StoryKit: Action-kind dialogue choices + weapon outfit slot
- ForgeKit/Hireling: mark script-owned NPCs so they can't be clone-adopted
- SplitScene: refuse a solo unflipped-guest crossing instead of wedging
- RM18b: root cause CONFIRMED at the clamp rail — restore the guard, add a pose sampler
- RM18b: root-cause the merchant's backward spine arch + guard behind a live toggle
- StoryKit: keep dialogue-menu highlight in place across leaf replies
- AO4-2/AO4-7+8: document the Mobile=true merchant rule; explain the stash.Graph write
- AO4-13: state the look-range change as tidy-up, not as two bugs (review CHANGES)
- AO4-13: one look range in NpcLookFollow, and no first-frame turn to world origin
- AO4-12: escape control characters in the qeventdb JSON dump
- AO4-10: qeventadd's auto-registered signatures are not savable
- AO4-7+8: cut orphaned dialogue nodes loose on rebuild, deferred under a live talk
- AO4-6: keep the stock-table sweep off the merchant's spawn frame
- AO4-5: drop a Train row that has no train node instead of showing a dead one
- AO4-4: quest-event presence is stack > 0, and stack 0 prints as DORMANT
- AO4-3: verify SideLoader actually took our template after ApplyTemplate
- AO4-2: a static merchant is refused, not silently shopless
- AO4-1: warn on a replicated SideLoader body with no local template (interim)
- Callback dialogue leaves (
0.1.3→0.1.4, additive;COMPAT_SINCEunchanged at0.1.3).ChoiceKind.Action+Choice.Action(id, text, replyText, actionId): a Reply leaf that first runs a host-registered callback, and whose reply text is REPLACED at execution time when that callback returns a non-empty string — so a row can answer with live state the spec never knew. Legal at any menu depth (unlike Train/Shop there is no singleton node to point at).StoryKit.ChoiceActions.Register(specId, actionId, Func<Character,string>)/Unregister(specId)/TryGet/Has. Keyed by SPEC id so a consumer minting per-spawn-unique specs (DangerousRoads'dr_merchant_<n>) sheds the whole table in one call —NpcRegistry.Unregisternow does exactly that.MenuPlanner.Plan(…, Func<string,bool> handlerAvailable): an Action row with no registered handler is dropped at plan time (MenuDropReason.ActionNoHandler), the Shop row'sShopNoMerchanttreatment. An Action row emits an ActionNode + its Reply statement (menu[i] → action → reply → greeting/parent menu) while still consuming exactly ONE out-connection of the menu, so theavailableChoicesindex-match holds.SpecValidationrefusesKind=Actionwith an emptyActionId.RunActionTaskresolves the talking player at EXECUTION time (theOpenTrainerTaskprecedent, never a serialized binding) and calls the handler inside a try/catch — a consumer's throw logs and the conversation still reaches the statement.
OutfitSpec.WeaponName: the rolled outfit can hold a weapon, resolved by display name through the same loose item-name ladder as chest/helmet/boots (overridesNpcSpec.WeaponId; an unresolvable name warns and is skipped, never the spawn).- Every body
NpcRegistry.RigCharactertouches is now stampedForgeKit.ScriptedBody(owner=StoryKit,reason=story NPC '<id>') — the "do not adopt this body" marker — plus theSpawnAtAndGetpath that deliberately skips theOnSpawncallback, which stamps it itself. That covers every master-side spawn. On a guest,NpcDirector.EnsureGuestRigswalksNpcRegistry.Specs(), so STATIC specs (Maren, the trainers) are marked there too, but a per-spawn spec minted at card time (dr_merchant_<n>) is registered master-side only and its guest replica is not marked — harmless today, since the only consumer refuses on a non-master. Rationale and the live incident it closes:src/ForgeKit/ScriptedBody.csand the ForgeKit 0.4.10 changelog entry. TorsoLookGuard(+ pureStoryKit.Core.TorsoLook.ShouldMute, unit-tested): mutes vanilla's torso look-at pitch on StoryKit bodies in the ONE case vanilla left undamped.Character.UpdateLateAnim2rotates two spine bones from a pitch clamped to+50°(up to ~60° of backward arch, legs untouched) and only damps itif (m_currentWeapon && !flag)— our NPCs are unarmed, so a locked target pegs the clamp and it unwinds at 2°/s. An armed, aiming, sprinting or unlocked body is left entirely to vanilla. Membership is an instance-id set maintained at spawn/despawn, not aGetComponentin a LateUpdate that runs for every Character in the scene. Ships behindTorsoLookGuard.Enabled(default off) soroadsmerchant torsofix on|offcan A/B it live — road-merchant-testplan RM18b, not yet graded in game.DialogueSelectionMemory:DialoguePanelno longer snaps the menu highlight back to the top row when a leafChoice.Replyloops back to the same parent menu — it stays on the row you picked. Navigating to a genuinely different menu (Back, a submenu, a new NPC) still resets to the top.- Random looks:
NpcSpec.RandomVisuals(gender/skin/head/hair/hair colour rolled per spec id + session salt, bounds from the game'sCharacterVisualsPresets) andNpcSpec.OutfitPool/OutfitSpec { ChestName, HelmetName, BootsName }(one outfit rolled per spawn, names resolved through the apostrophe-tolerant item-name ladder; an unresolvable piece warns and is skipped). PureOutfitRoll.Pick/VisualRoll.Seed|Roll|Roll01in Core, unit-tested. NpcRegistry.IsInDialogue(id): true while the NPC's dialogue tree is running, the game lists it as in conversation, or its shop has a buyer — for consumers that walk the NPC.- Mobile / combat / merchant NPCs:
NpcSpec.Mobile(SideLoader melee AI, unpinned body),AiSpec,CombatSpec(health/protection/resists/damage multiplier/targetable factions by NAME),Factionby name,BackpackNameby display name,MerchantSpec(vanillaMerchantgraft, caravan stock table lookup, fallback items, refresh rate),Choice.Shop→ a realShopDialogueActionnode. NpcRegistry.SpawnAtAndGet/TryGetLive/TryGetMerchant/Rebuild/ReplaceGreetings.NpcRegistry.Unregister(id): despawn if live,SL_Character.Unregister()the template, remove the entry — for consumers minting per-spawn spec ids (the road merchant).- Stats re-enable:
StoryNpcTemplate(anSL_CharacterTrainersubclass) keepsCharacterStatsenabled for Mobile/Combat specs instead of SideLoader's unconditional disable. - Dev verb
npcmerchanttest [despawn]— the built-in proving merchant (live-verify owed). - Existing static NPCs are unchanged (no Mobile → same pin/NoFall/snap/stats-strip path).
0.1.3 — 2026-08-19
- StoryKit: quest-event catalog tools — enumerate, dump and toggle by name
- Review follow-ups on the F3c leaf-kit wave (M1, M2, L3, L5, L6)
- Static-analysis wave F3c: leaf-kit fixes (A7-1/2/4/8/9/13/14/15/16/20/23/24/25/27)
- Coercion wave: every ForgeKit integration completes cleanly through the TUI seam
- 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.1.2 — 2026-08-11
- Merge branch 'main' into fix/sa-0808-kits
- Static-analysis 2026-08-08 wave D-kits: P2-13/P2-15 + the kit P3 group
- Static-analysis 2026-08-08 wave E: close the test-debt list (§4)
- Maren teaches: nested tutorial dialogue + StoryKit submenus
- W4: fixes for everything D1 found, plus the join-race P1
- StoryKit: fake-null cleanup across the NPC rig and the recon taps
0.1.1 — 2026-08-02
- Cleanup: collapse three repeated verb preambles into helpers that already had a home
- ForgeKit/SkillKit/StoryKit: fix five guards that could never fire, and stop the recovery verb lying
- Docs sweep: archive, condense, and validate the whole documentation tree
0.1.0 — 2026-07-30
- Thunderstore release prep: changelogs + real category tags for BW
- Thunderstore release prep: BW/SkillKit/StoryKit manifests + descriptions
- Icon pipeline: condition generation on real Outward art, restyle every icon
- Review fixes for b5118e3 MP wave: real corpse-view mute (group block), QESYNC poison purge, CHAR-GUARD player exemption + ghost identity check
- 2026-07-27 MP session closeout: NK-F3 guest-join fix (QuestEventSyncGuard + unconditional srecon signatures + save purge), V-PARKLEAK corpse-view mute, AddCharacter dup-UID guard, [DIAG] loading line; session handoff + testplan/STATUS updates (428->352 queue burn)
- Review must-fixes: plain-NPC fallback safety, empty choice menu, Dialogue guard, owner side-table leak
- StoryKit T2/T4: kill-switch is recoverable; recon verbs route through VerbHost
- StoryKit T1/T3/T5: plain-dialogue NPCs, on-demand late registration, internal OpenTrainerTask
- Maren MP review fixes: scene-scope the guest warning, adopt the replica
- Maren MP: converge placements against CharacterManager + watch the room
- StoryKit: per-NPC toggle for NpcLookFollow; Maren stands static
- Evening fix batch: Bugs 44-47 + 39 + unequip-hint nit (built, NOT live-verified)
- Integrate verb re-homing wave + 2026-07-20 SP session results + fix batch
- fix: high-severity wave from the 2026-07-19 static-analysis review
- Maren: relocate default spawn to Cobalt's spot + StoryKit ground-snap
- Guest pets Phase A (M0-M5): guests can tame, fight, and buy the skill tree
- Small-mods bundle (review 2026-07-14): TreeLayout breakthrough violations now hard-refuse (Cobalt's ruling), Hireling recruit/dismiss master-gated, AggroKit cause-stack maintenance skipped when observation is off, StoryKit verbs through VerbHost
- StoryKit: NPCs/trainers/dialogue as a kit — Maren goes live in Cierzo
0.1.0 — initial release
- Add an NPC to Outward as plain data: a consumer describes placement, appearance, dialogue, and (if the NPC is a trainer) the skills it sells, and StoryKit builds the character, spawns it at a fixed spot, and wires the conversation — no custom UI, no NodeCanvas hand-editing.
NpcDirectorspawns the registered NPC at player-ready on every scene load — master-only in co-op, duplicate-safe.DialogueBuildercompiles a described conversation into a real NodeCanvas graph, reusing SideLoader's own trainer dialogue nodes.- A described skill tree compiles and sells from the vanilla Trainer window — no custom shop UI.
- NPCs, trainers, and dialogue only. No quest/story-event authoring API in this release.
