Skip to content

Quest System

Summary: Tag-graph quest system — the player's quest state IS a replicated FGameplayTagContainer, and all narrative progression is expressed as tag grants. There is deliberately no objective-checklist engine and no quest log: the Compendium (journal of discovered lore entries) is the player-facing record. UQuestManagerComponent on GameState holds the global registry; UPlayerQuestComponent on PlayerState tracks per-player tags. Dialogue (DlgSystem), item pickups, lore actors, and world triggers all funnel into the same server-authoritative AddQuestTag path.

Table of Contents


Design Philosophy

The tag-graph model is final (WorldSystemsScalingReview RULING-8, Oliwer 2026-07-07):

  • Tags are the quest state. A quest's progression is the set of Quest.* tags a player has. There are no per-objective booleans, no counters, no stage machine — a "stage" is just a tag someone granted.
  • No quest log. The GDD's narrative stance (story through environments, items, echoes) means the player-facing surface is the Compendium: lore entries unlock when their RequiredTag is granted, and the journal doubles as the quest record.
  • Everything funnels through AddQuestTag. Dialogue events, item pickups (FQuestFragment), lore actors (ALoreFragmentActor), world-hint triggers, and quest triggers all end up in the same server-authoritative grant path, which also resolves compendium unlocks.
  • NPCs branch on tags. Dialogue graphs check tags via UQuestDialogueCondition rather than querying quest objects. FromSoft-style: the world reacts to what you've done, it doesn't enumerate what's left to do.

Do NOT add checklist-engine features (visibility conditions, per-objective completion flags, prerequisite chains) — they were built speculatively once, never read by anything, and deleted in the 2026-07 Phase 1 cleanup.


Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                    AEternalGameState                         │
│  ┌───────────────────────────────────────────────────────┐  │
│  │           UQuestManagerComponent (Global)              │  │
│  │  • Quest Data Asset Registry (asset-registry scan)     │  │
│  │  • Compendium Entry Registry (RequiredTag → entry)     │  │
│  │  • Global Events (FGameplayTagContainer, replicated)   │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│                   AEternalPlayerState                        │
│  ┌─────────────────────────────────────────────────────── ┐ │
│  │  UPlayerQuestComponent                                  │ │
│  │  • QuestTags / ActiveQuests / CompletedQuests           │ │
│  │  • all replicated COND_OwnerOnly                        │ │
│  └─────────────────────────────────────────────────────── ┘ │
│  (GetCompendiumSystem() accessor only — see below)           │
└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐
│              AEternalPlayer (PlayerController)               │
│  ┌─────────────────────────────────────────────────────── ┐ │
│  │  UCompendiumSystem                                      │ │
│  │  • Discovered entries (TMap, NOT replicated)            │ │
│  │  • Read/unread state, persistence save/load             │ │
│  └─────────────────────────────────────────────────────── ┘ │
└─────────────────────────────────────────────────────────────┘

Note the compendium's home: UCompendiumSystem is created on the PlayerController (EternalPlayer.cpp constructor). AEternalPlayerState::GetCompendiumSystem() is just an accessor. Older docs claimed it lived on PlayerState — wrong.

Key Design Principles

Principle Implementation
Tag-Based All quest state tracked via FGameplayTag
Server-Authoritative All mutations authority-gated; clients go through Server RPCs
Dual Components Global registry + per-player tracking
Data-Driven Quests defined entirely through data assets
Single Grant Funnel Every grant path converges on AddQuestTag → compendium unlock → client toast

Tag Conventions

Authored tags follow this shape (see Config/Tags/GameplayTags.ini, Quest.* block):

Quest.[Name]                          root quest tag (registered to a UQuestDataAsset)
Quest.[Name].Discovery.[How]          discovery grants (e.g. Quest.BeautifulFountain.Discovery.NPCMentioned)
Quest.[Name].Objective.[What]         objective grants (e.g. Quest.BeautifulFountain.Objective.PickedUpHead)
Quest.[Name].Completed                granted automatically by CompleteQuest
  • CompleteQuest synthesizes the completion tag by string append: QuestTag + ".Completed" (PlayerQuestComponent.cpp). The tag must exist in the ini for the grant to resolve.
  • There is no .Stage. segment. Generic Quest.Stage.* / Quest.SubEvent.* scaffolding tags and the GetQuestDiscoveryTag/GetQuestCompletionTag helpers that derived them were dead code from an idealized early model and were deleted (2026-07 Phase 1). Shipped content never used them.
  • Tags are hand-authored in the ini today; the NarrativeAuthoringWorkflow manifest→stamp pipeline will generate them (see ImplementationDocs/NarrativeAuthoringWorkflow.plan.md).

Quest Flow

Quest Lifecycle

Discovery                        Active                         Completed
    │                              │                               │
    ▼                              ▼                               ▼
┌─────────┐                  ┌──────────┐                   ┌───────────┐
│ Trigger │                  │ Progress │                   │  Rewards  │
│ • NPC   │─────StartQuest()─▶│ • Tags   │──CompleteQuest()─▶│ • Items   │
│ • Item  │                  │ • Dialog │                   │ • Consume │
│ • Zone  │                  │          │                   │   Items   │
└─────────┘                  └──────────┘                   └───────────┘

State Transitions

AddQuestTag(Tag)                      [authority only]
    │
    ├─ Reject invalid / already-present tag
    ├─ Add to QuestTags container
    ├─ Broadcast OnQuestTagAdded
    ├─ CheckForTagDrivenItemConsumption: consume inventory items whose FQuestFragment.ConsumeOnTag == Tag
    ├─ QuestManager registry lookup: does Tag unlock a compendium entry?
    │     └─ yes → CompendiumSystem->AddEntry + ShowCompendiumUnlock (diegetic: entry title alone)
    ├─ QuestManager deepen lookup: does Tag deepen an existing entry?
    │     └─ yes → CompendiumSystem->DeepenEntry (content grows, entry flips back to unread — no toast)
    └─ Replication (COND_OwnerOnly) → OnRep on owning client

StartQuest(QuestTag)                  [authority only]
    │
    ├─ Quest must be registered in QuestManager
    ├─ Reject if already active or completed
    ├─ Add to ActiveQuests
    └─ Broadcast OnQuestStarted

CompleteQuest(QuestTag)               [authority only]
    │
    ├─ Quest must be registered and active, not already completed
    ├─ Move ActiveQuests → CompletedQuests
    ├─ AddQuestTag(QuestTag + ".Completed")
    ├─ Broadcast OnQuestCompleted
    ├─ CheckForQuestItemConsumption (FQuestFragment items)
    └─ GrantQuestRewards (item pipeline — see Quest Rewards)

Grant Paths (all converge on AddQuestTag)

Path Mechanism
Dialogue UQuestDialogueEvent → Server RPCs on UPlayerQuestComponent
Dialogue item hand-over UGiveItemDialogueEvent → Server_GrantDialogueNodeItems (grants items + GrantedMarkerTag; see Dialogue Integration)
Item pickup/drop FQuestFragment::OnPickup/OnDrop (bound to inventory events)
Lore actor ALoreFragmentActor (LoreUnlockTag on examine)
World trigger BP_StartQuestTrigger (Blueprint); AQuestTriggerActor (volume that opens a dialogue)
World hints WorldHintComponent → Server_AddQuestTag
Enemy kill credit AEternalEnemy::GrantKillCreditQuestTag() on death: for every damage contributor (not only the killing blow) grants FEnemyData::KillCreditQuestTag, runs CompleteQuest on KillCreditCompleteQuestTag (idempotent), and fires AEternalPlayer::Client_NotifyBossDefeated when BossBannerEpithet is set — the banner shows even for enemies with no quest tags. See Enemy AI
Encounter gate AEncounterGateActor::GrantOnOpenTag — granted to every player on an announced open only; silent initialization opens (already-cleared restore) never grant. See Dungeon System

Component Responsibilities

UQuestManagerComponent (Global)

Lives on AEternalGameState, server-authoritative.

Method Purpose
LoadAllQuestDataAssets() Asset-registry scan at BeginPlay; builds all four registries (quests, entry unlocks, deepen stages, category display assets)
RegisterQuest(QuestData) Add quest to registry (also indexes its compendium entries)
GetQuestDataByTag(Tag) Lookup quest data asset
GetCompendiumEntryByTag(RequiredTag) Fast entry lookup used by AddQuestTag
GetCompendiumDeepenByTag(RequiredTag) Fast deepen-stage lookup used by AddQuestTag
GetCompendiumCategoryByTag(CategoryTag) Display data (UCompendiumCategoryDataAsset) for a journal tile
StartGlobalEvent(Tag) / EndGlobalEvent(Tag) Activate/deactivate world event
IsGlobalEventActive(Tag) Check event status

UPlayerQuestComponent (Per-Player)

Lives on AEternalPlayerState, server-authoritative. Every mutation early-returns unless GetOwnerRole() == ROLE_Authority.

Method Purpose
AddQuestTag(Tag) Add tag (authority) — the single grant funnel
RemoveQuestTag(Tag) Remove tag (authority)
StartQuest(Tag) / CompleteQuest(Tag) Quest lifecycle (authority)
Server_AddQuestTag / Server_RemoveQuestTag / Server_StartQuest / Server_CompleteQuest Server RPC mirrors — the ONLY correct call surface from client-side code (dialogue, UI)
HasQuestTag(Tag) Hierarchical check (see Gotchas)
IsQuestActive(Tag) / IsQuestCompleted(Tag) Status queries
GetQuestTags() / GetActiveQuestTags() / GetCompletedQuestTags() Container access

Persistence goes through PersistenceHelpers / PersistenceComponent, not through this component — it has no GetSaveData/LoadFromSaveData of its own (older doc claimed otherwise).

Quest Item Consumption

Two consumption moments, both on UPlayerQuestComponent and both shaped the same way:

CompleteQuest(QuestTag)                      AddQuestTag(Tag)
    │                                            │
    └─ CheckForQuestItemConsumption(QuestTag)    └─ CheckForTagDrivenItemConsumption(Tag)
        │                                            │
        ├─ ResolveInventory()                        ├─ ResolveInventory()
        ├─ COLLECT items whose                       ├─ COLLECT items whose
        │    FQuestFragment.RelatedQuestTag == Tag   │    FQuestFragment.ConsumeOnTag == Tag
        └─ then for each: OnQuestCompleted           └─ then for each: OnConsumeTagGranted
              └─ bConsuming = true                         └─ bConsuming = true
                 IItemContainer::Execute_RemoveItemFromContainer
                 bConsuming = false
  • ResolveInventory() re-resolves through IPlayerInventory on the owning PlayerState on demand. The BeginPlay inventory binding can miss (the PlayerController links to the PlayerState late), and a null cached pointer used to make consumption a silent no-op.
  • Collect before removing. Consumption removes fast-array entries; removing mid-iteration invalidated the iteration.
  • Removal goes through IItemContainer::Execute_RemoveItemFromContainer, so grid occupancy clears — a raw entry removal left a ghost footprint in the inventory grid.
  • bConsuming guard: the removal fires the same OnItemRemoved path a player drop does, and FQuestFragment::OnDrop must not strip the objective tag for a consumption.
  • ConsumeOnTag is for items narratively handed over mid-quest (the recipient "takes" the item at that beat) without waiting for completion; RelatedQuestTag + bConsumeOnQuestComplete remains the completion path.

Quest Data Model

UQuestDataAsset

Property Purpose
QuestName Display name (also the journal tile title for legacy quest-tag-grouped entries)
QuestDescription Full description text
QuestBanner UI banner image
QuestTag Unique identifier tag
bSuppressCompletionBanner Skip the quest-completed event banner — for quests whose completion moment is already announced by something bigger (a boss-defeated banner); two stacked banners cheapen both
Objectives Array of objectives (display metadata for tags)
Rewards Array of rewards, granted on CompleteQuest
CompendiumEntries Lore entries unlocked during this quest (inline — ruled N3, no per-entry assets)
CompendiumDeepenStages Deepen stages this quest's tags apply to already-unlocked entries (theirs or another quest's)

The checklist-era fields (DiscoveryCondition, CompletionCondition, bIsRepeatable, bIsHidden, RequiredGearScore, FQuestCondition, FQuestSaveData) were never read by anything and were deleted in the 2026-07 Phase 1 cleanup. Do not reintroduce.

FQuestObjective

Property Purpose
DisplayName Short objective name
Description Objective details
CompletionTag The tag whose grant represents this objective
bIsOptional Design metadata (nothing enforces it — tags are the truth)

FQuestReward

Property Purpose
DisplayName Reward display name (falls back to the item's name fragment if empty)
Description Reward description
ItemTemplate TSoftObjectPtr<UItemManifestDataAsset> to grant
ItemAmount Quantity (stack count for stackables)

Quest Rewards

CompleteQuest → GrantQuestRewards (server-side, PlayerQuestComponent.cpp):

  1. Load each reward's ItemTemplate manifest asset.
  2. Copy the manifest, apply stack count, and run UItemGenerationLibrary::GenerateItemProperties — the same generated-item flow as vendor stock, so gear rewards roll modifiers server-side.
  3. Manifest() the UItemObject (outer = owning PlayerController, matching the pickup path) and TryAddItemToContainer on the player's inventory.
  4. Inventory full → the reward is world-dropped at the player's feet via AEternalGameState::Server_RequestSpawnItem instead of being lost.
  5. Client_ShowRewardNotification toast on the owning client.

Compendium System

UCompendiumSystem

Journal/lore system on the PlayerController (AEternalPlayer).

Method Purpose
AddEntry(Entry) Add new compendium entry (stamps DiscoveryTime, dedups by EntryID)
UpdateEntry(ID, Content) Replace entry text — always flips the entry back to unread
DeepenEntry(Stage) Apply a deepen stage: compose (append/replace) onto existing content via UpdateEntry; refuses (warn) if the entry isn't unlocked yet
MarkEntryAsRead(ID) / Server_MarkEntryAsRead(ID) Clear unread indicator (server RPC for client UI)
GetEntry(ID) / GetAllEntries() Retrieval (chronological, oldest first — the list reads as a story)
GetEntriesByQuestTag(Tag) Filter by quest (hierarchical tag match)
GetUnreadEntries() / GetRecentEntries(Count) Filtered views
GetSaveData() / LoadFromSaveData(Entries) Persistence surface (used by PersistenceHelpers)

Entries is an FCompendiumEntryArray (FastArraySerializer) replicated COND_OwnerOnly (ruling N4, shipped) — the owning client's journal live-updates via PostReplicatedAdd/Change/Receive callbacks. Note the OnRegister OwnerComponent = this re-bind: without it, archetype property copy points the callbacks at the CDO and a remote client's journal never updates.

FCompendiumEntry

Property Purpose
EntryID Unique identifier (FName)
Title Entry title
Content Full text content (single FText — class-voiced variants deferred, ruling N6)
RelatedQuestTag Authoring quest (provenance + grouping fallback)
CategoryTag Player-facing journal tile (Compendium.Category.*, thematic/locational — "The Ashwoods"). Unset → groups under RelatedQuestTag and titles from QuestName. Display data: UCompendiumCategoryDataAsset
RequiredTag The tag whose grant unlocks this entry (exact match)
DiscoveryTime When discovered (runtime, sort key — never rendered)
bHasBeenRead Read state (runtime; also re-set false by deepening)

FCompendiumDeepenStage

Deepening = the activity-safe forward pull: an already-unlocked entry gains content when a later tag lands, and flips back to unread — the badge is the whole presentation (no toast, no counter, per the no-completion-metrics pillar). Stages live on UQuestDataAsset.CompendiumDeepenStages and may target another quest's entry.

Property Purpose
EntryID The unlocked entry to deepen
RequiredTag Exact tag whose grant applies the stage
Content New text
bAppend Append below existing content (default) or replace outright

Authoring flows through the quest manifest (deepen[] + categories[] sections — Tools/Narrative/SPEC_FORMAT.md); the narrative validator checks deepen keys like unlock keys and errors on a CategoryTag with no display asset.

Category Widget ViewModel Binding

UCompendiumController injects the ViewModel into each UCompendiumCategoryWidget at creation time via SetViewModel. Injection before construction just stores the ViewModel; NativeConstruct performs the binding. Looking the ViewModel up by widget name in the MVVM collection is now only a fallback for Blueprint-placed instances, and every ViewModel dereference early-returns on null.

The name lookup could not stay the primary path: the collection is keyed by widget name, and a second world in the same GameInstance recreates the widget under a uniquified name. The lookup then misses and the tile silently renders unbound — a bug that only appears on the second session, never the first.

Red-Text Formatting

Compendium entry rich-text keyword formatting (red-text keywords etc.) lives entirely in asset data: Content/DataAssets/Compendium/DefaultCompendiumFormattingRules.uasset, referenced by W_CompendiumEntry. Adding a keyword is an asset edit, not a recompile — the old hardcoded C++ table in CompendiumFormattingRules.cpp was removed (2026-07 Phase 1). If no rules asset is wired, content renders raw.


Dialogue Integration

The DlgSystem bridge is five thin classes under Quest/Dialogue/ (+ VendorDialogueEvent).

Walking Away Closes the Conversation

Movement is deliberately never blocked during dialogue (owner ruling: genre-standard PoE/Diablo feel). Walking away therefore is the close gesture, and UDialogueController treats it as one.

A 0.25s timer poll — not a tick — measures distance to the speaker. Past WalkAwayEndDistance (600, EditDefaultsOnly), or if the speaker despawns, it calls TryEndDialogue. The poll is cleared inside TryEndDialogue, so it cannot outlive the conversation.

Cinematic dialogues (null instigator) never start the poll — there is no speaker to walk away from.

UQuestDialogueCondition

Gates dialogue nodes on quest state. Runs on the owning client against the replicated QuestTags container — safe because quest tags replicate owner-only.

Dialogue Node
    │
    └─ QuestDialogueCondition
        ├─ QuestTag: Quest.BeautifulFountain.Discovery.NPCMentioned
        └─ bRequireTag: true (must have to show this dialogue)

UQuestDialogueEvent

Triggers quest actions from dialogue. Dialogue playback is client-side (UDialogueController runs the loop on the interacting client; W_Dialogue renders it), so the event routes through the Server RPCs — calling the authority-gated methods directly from a dialogue event was a silent no-op on dedicated servers (fixed 2026-07, guarded by AQuestDialogueEventTest).

Event Type Routed To
AddQuestTag Server_AddQuestTag
RemoveQuestTag Server_RemoveQuestTag
StartQuest Server_StartQuest
CompleteQuest Server_CompleteQuest

UGiveItemDialogueEvent

UDlgEventCustom that hands the player items when its node is entered, surfaced as a received-item toast: ItemsToGive (all granted), or BuildGatedItems (only the entry whose RequiredKit the player's active preset granted — every entry as a fallback when none match; supersedes ItemsToGive when set), plus a required GrantedMarkerTag as the idempotency guard (held → skip; granted after the give, so re-entering the node or a second appearance of the same NPC never double-gives). Because the RPC carries only a node identity, the marker is the sole replay protection: the server refuses a give whose event has no marker, and UNarrativeValidator errors on the node.

The grant is authority-routed like UQuestDialogueEvent, but the Server RPC (Server_GrantDialogueNodeItems(Dialogue, NodeGUID, EnterEventIndex)) takes only the event's identity — the dialogue asset, the active node GUID and the enter-event index — and the server re-reads the authored item set from that node (GrantAuthoredDialogueItems). A client-supplied item list was a dupe vector. Items are manifested and generated through the same GrantGeneratedItemToInventory helper as quest rewards (modifiers roll server-side, drop-at-feet when full).

UQuestDialogueTextArgument

Dynamic text substitution in dialogue lines (quest name/description/objective text, compendium entry content).

IQuestParticipant

Quest/Interfaces/QuestParticipant.h — one method, GetPlayerQuestComponent(). Implemented by AEternalPlayerState (the dialogue participant for the player side) and AEternalNPC.


Multiplayer

  • Authority: every quest/compendium mutation is authority-gated; client-side systems (dialogue, UI, cheats) must use the Server* RPCs.
  • Replication: QuestTags / ActiveQuests / CompletedQuests replicate COND_OwnerOnly — other players never see your quest state. Global events replicate to everyone.
  • Compendium: server + persistence only today (see warning above; N4 fix scheduled).
  • Trust surface: the Server RPCs perform no server-side plausibility validation (a modified client could grant itself any quest tag). Accepted for the current co-op trust model; revisit if that model changes.
  • Tests: AQuestStartTest, AQuestCompleteTest, AQuestDialogueEventTest in Content/Maps/FunctionalTests/ReplicationTestMap.umap (listen server + client PIE).

Public Contracts

Events (UPlayerQuestComponent)

Delegate Payload When Fired
OnQuestTagAdded FGameplayTag Tag added
OnQuestTagRemoved FGameplayTag Tag removed
OnQuestStarted FGameplayTag Quest begins
OnQuestCompleted FGameplayTag Quest finishes

Events (UQuestManagerComponent)

Delegate Payload When Fired
OnGlobalEventStarted FGameplayTag World event begins
OnGlobalEventEnded FGameplayTag World event ends

Events (UCompendiumSystem)

Delegate Payload When Fired
OnEntryAdded FCompendiumEntry New entry discovered
OnEntryUpdated FCompendiumEntry Entry content changed
OnEntryRead FCompendiumEntry Entry marked as read
OnCompendiumReloaded (none) Bulk reload after LoadFromSaveData

Replication

Property Replication
QuestTags ReplicatedUsing = OnRep_QuestTags, COND_OwnerOnly
ActiveQuests ReplicatedUsing = OnRep_ActiveQuests, COND_OwnerOnly
CompletedQuests ReplicatedUsing = OnRep_CompletedQuests, COND_OwnerOnly
ActiveGlobalEvents ReplicatedUsing = OnRep_ActiveGlobalEvents

Gotchas

  • HasQuestTag is hierarchical (FGameplayTagContainer::HasTag): holding Quest.X.Discovery.Y answers true for HasQuestTag(Quest.X). Consequently AddQuestTag(Quest.X) no-ops if any child of Quest.X is already held (the duplicate check uses the same hierarchical match). Grant roots before children, or treat root tags as implied. The planned narrative validator will flag graphs that depend on grant-root-after-child ordering.
  • Completion tags are string-synthesized — QuestTag + ".Completed" must exist in GameplayTags.ini or the grant silently resolves to an invalid tag and is rejected.
  • Never call AddQuestTag/StartQuest/CompleteQuest from client-side code — they silently no-op off-authority. Use the Server* RPCs.
  • Dialogue gives never trust the client's item list. UGiveItemDialogueEvent sends the node identity only; the server re-reads the authored items. Keep it that way when adding new dialogue-driven grants.
  • Every give-node needs a GrantedMarkerTag. A node identity is replayable from a modified client, so a markerless give would be farmable gear. The server refuses it and the narrative validator reports it as an error.
  • AQuestTriggerActor poll-scans after BeginPlay. A pawn that is deferred-spawned already inside the volume produces no begin-overlap, so the actor runs a bounded ScanForOverlaps (InitialScanTimer, 0.5s, up to 20 attempts via InitialScanAttempts) rather than relying on the overlap event alone.

Source References

Component Location
UQuestManagerComponent Quest/QuestManagerComponent.h
UPlayerQuestComponent Quest/PlayerQuestComponent.h
UQuestDataAsset Quest/QuestDataAsset.h
FQuestObjective, FQuestReward, FCompendiumEntry Quest/QuestTypes.h
UCompendiumSystem Quest/CompendiumSystem.h
UQuestDialogueCondition Quest/Dialogue/QuestDialogueCondition.h
UQuestDialogueEvent Quest/Dialogue/QuestDialogueEvent.h
UGiveItemDialogueEvent, FBuildGatedItem Quest/Dialogue/GiveItemDialogueEvent.h
AQuestTriggerActor Quest/Actors/QuestTriggerActor.h
Kill-credit fields (KillCreditCompleteQuestTag, BossBannerEpithet) AI/Data/BaseEnemyDataAsset.h; consumer AI/Enemy/EternalEnemy.h
UQuestDialogueTextArgument Quest/Dialogue/QuestDialogueTextArgument.h
IQuestParticipant Quest/Interfaces/QuestParticipant.h
FQuestFragment Inventory/Items/Fragments/ItemFragment.h
UCompendiumFormattingRules Data/CompendiumFormattingRules.h
Replication tests Tests/QuestReplicationTest.h + Private/Tests/Replication/QuestReplicationTest.cpp


Recent Changes

Date Change Impact
2026-08-20 Documented UGiveItemDialogueEvent (identity-only Server RPC, build-gated gives, marker tag), the reworked item consumption (ResolveInventory, collect-then-remove, Execute_RemoveItemFromContainer, bConsuming guard), FQuestFragment::ConsumeOnTag, bSuppressCompletionBanner, kill-credit / encounter-gate grant paths, and AQuestTriggerActor's bounded poll-scan Dialogue hand-overs, boss-kill quest completion and mid-quest item consumption were shipping undocumented; the old consumption diagram described a path that silently no-op'd on a late inventory bind
2026-08-06 Compendium category widgets now receive their ViewModel by injection (SetViewModel) from UCompendiumController; MVVM name lookup demoted to a Blueprint-placed fallback; all ViewModel dereferences early-return on null Name-keyed lookup broke on a second world in the same GameInstance — the recreated widget's name is uniquified, so the tile rendered unbound on every session after the first
2026-07-23 Walk-away auto-close (21bedd104): a 0.25s timer poll ends the conversation past WalkAwayEndDistance (600) or on speaker despawn; cinematic dialogues never start the poll Confirms the ruling that movement is never blocked during dialogue — walking away is the close gesture, and it no longer leaves an orphaned dialogue window
2026-07-07 Phase 1 cleanup (NarrativeAuthoringWorkflow): vestige deletion, dialogue-event server RPC routing, reward granting wired, red-text rules moved to asset data, doc rewritten around tag-graph philosophy Checklist-era fields gone; dedicated-server dialogue events fixed
- Initial documentation -