Gossip Propagation System¶
The Gossip System enables NPCs to spread knowledge to each other organically. When an NPC witnesses an event, that knowledge can propagate to nearby NPCs through gossip, creating a living information network where news travels realistically through the game world.
Overview¶
graph TD
A[World Event] --> B["KnowledgeObservationSystem<br/>(available, not default-registered)"]
B --> C[KnowledgeManager.add]
A2[Admin / Content Pack] --> C
C --> D[KnowledgeLearnedEvent]
C --> E[NPC Knowledge Store]
E --> F[GossipSystem]
F --> G{"Same room? NEUTRAL+ tier?"}
G -->|Yes| H[KnowledgeManager.propagate]
H --> I[GossipSpreadEvent]
H --> J[Receiver's Knowledge Store]
G -->|No| K[Skip]
Two components are involved:
- KnowledgeObservationSystem — Converts world events into NPC knowledge entries. This system exists in
maid_stdlib.knowledge.observationbut is not registered by default;ClassicRPGContentPackonly registersGossipSystem. Content packs can register it manually if automatic knowledge creation from world events is desired. - GossipSystem — Spreads existing knowledge between NPCs who share a room (registered by
ClassicRPGContentPack)
KnowledgeObservationSystem¶
This system subscribes to world events and creates knowledge entries for NPCs who witness them.
Observed Events¶
| Event | Knowledge Created | TTL |
|---|---|---|
CombatStartEvent |
"X attacked Y" | 1 hour |
EntityDeathEvent |
"X was defeated by Y" / "X died" | 2 hours |
ItemPickedUpEvent |
"X picked up item" | 30 minutes |
ItemDroppedEvent |
"X dropped item" | 30 minutes |
RoomEnterEvent |
"X arrived" | 15 minutes |
RoomLeaveEvent |
"X departed" | 15 minutes |
How It Works¶
When an event fires, the system:
- Identifies NPCs in the same room as the event
- Creates a
KnowledgeEntrywith appropriate subject, predicate, and object - Stores it via
KnowledgeManager.add() - The manager emits a
KnowledgeLearnedEvent
# Example: A combat event creates knowledge for nearby NPCs
KnowledgeEntry(
subject="warrior_player",
predicate="attacked",
object="goblin_chief",
confidence=1.0, # Witnessed firsthand
source_npc_id="bartender",
source_type="observed",
ttl_seconds=3600, # Expires in 1 hour
)
GossipSystem: Propagation Mechanics¶
The GossipSystem is an ECS System that runs each tick and spreads knowledge between NPCs.
Pair Selection¶
Every rebuild_ticks (default 30), the system rebuilds its gossip pair cache:
- Scans all NPCs with a position component
- Groups NPCs by room
- Creates pairs from NPCs sharing the same room
- Filters out pairs where the relationship is below
NEUTRALtier - Missing relationships (no relationship record) are treated as neutral and allowed to gossip
Exchange Process¶
Each tick, up to exchanges_per_tick (default 3) gossip exchanges occur:
- Select a pair from the cached list
- Validate — both NPCs still exist and share a room
- Check cooldown — pairs can't gossip again for
cooldown_ticks(default 10) - Select knowledge — pick sender's knowledge that the receiver doesn't already have
- Compute distortion — based on sender-receiver friendliness
- Build GossipPacket and call
KnowledgeManager.propagate() - Set cooldown and emit
GossipSpreadEvent
Confidence and Distortion¶
Each time knowledge is gossiped, its confidence degrades:
The distortion factor is derived from the relationship's friendliness dimension (on the -100 to 100 scale):
- High friendliness (100) → low distortion (0.05)
- Neutral friendliness (0) → high distortion (0.50)
- Low friendliness (-100) → max distortion (0.50, clamped)
Distortion is clamped between 0.05 and 0.50. Knowledge with confidence below the propagation threshold is not spread further.
Note
The GossipSystem (in maid_stdlib.knowledge.gossip) uses friendliness for
distortion. The separate KnowledgeGossipProcessor (in maid_classic_rpg.systems.npc.gossip)
uses trust with the formula 0.5 - 0.4 * trust (trust normalized to 0..1).
Missing relationships are treated as neutral (friendliness=0 → distortion=0.5,
trust=0.5 → distortion=0.3).
Stale Knowledge Expiry¶
Every maintenance_ticks (default 100), the system calls KnowledgeManager.expire_stale() to remove knowledge entries past their TTL. This prevents NPCs from gossiping about ancient events indefinitely.
Configuration¶
Engine Settings (MAID_MEMORY_ prefix)¶
# Maximum gossip exchanges per game tick
MAID_MEMORY_GOSSIP_BUDGET_PER_TICK=3
# Ticks before the same NPC pair can gossip again
MAID_MEMORY_GOSSIP_COOLDOWN_TICKS=10
Warning
These settings are defined in MemorySettings but are not currently passed to
GossipSystem by ClassicRPGContentPack. The system uses its own constructor
defaults. This is a known limitation.
GossipSystem Constructor Defaults¶
These are set when the system is instantiated (typically by the content pack):
| Parameter | Default | Description |
|---|---|---|
exchanges_per_tick |
3 | Max gossip exchanges per tick |
cooldown_ticks |
10 | Cooldown between same-pair gossip |
rebuild_ticks |
30 | How often to rebuild pair cache |
maintenance_ticks |
100 | How often to expire stale knowledge |
NPC Archetype Tuning¶
Individual NPC archetypes can define a gossip_tendency value:
# In archetypes.yaml
archetypes:
- archetype_id: bartender
display_name: Bartender
gossip_tendency: 0.8 # Very chatty
- archetype_id: hermit
display_name: Hermit
gossip_tendency: 0.1 # Rarely shares information
Note
The gossip_tendency field is defined on archetypes and loaded from YAML, but is
not currently used by GossipSystem to influence pair selection or exchange
likelihood. It is available for custom systems or future use.
Quest System Integration¶
The quest generation system uses a gossip confidence cap:
# In maid_classic_rpg.systems.quests.generation.constants
GOSSIP_CONFIDENCE_CAP = 0.9 # Quest-injected gossip starts at max 0.9 confidence
This prevents quest-generated knowledge from appearing as "absolute truth."
Events¶
KnowledgeLearnedEvent¶
Emitted when an NPC gains new knowledge (from observation or gossip):
@dataclass
class KnowledgeLearnedEvent(Event):
npc_id: str
subject: str
predicate: str
object: str
source_type: str # "observed" or "gossip"
confidence: float
GossipSpreadEvent¶
Emitted when knowledge successfully propagates between NPCs:
@dataclass
class GossipSpreadEvent(Event):
sender_npc_id: str
receiver_npc_id: str
subject: str
predicate: str
confidence: float
distortion_factor: float
Subscribe to these events to build reactive systems (e.g., quest triggers, NPC reactions).
Admin Commands¶
@gossip status¶
Shows the current state of the gossip system. The command retrieves the system via ctx.world.get_data("gossip_system"):
Warning
ClassicRPGContentPack registers the GossipSystem as an ECS system but does not
call world.set_data("gossip_system", ...). Unless a content pack explicitly sets
this world data key, @gossip status will report "Gossip system is not active."
@knowledge (related)¶
View and manage the knowledge that gossip spreads:
> @knowledge npc_bartender
Knowledge (8):
- warrior_player attacked goblin_chief (confidence=0.95)
- stranger arrived from east road (confidence=0.72)
...
> @knowledge add npc_bartender dragon spotted_near "mountain pass"
Added knowledge entry for Bartender.
Examples¶
Scenario: News Travels Through a Village¶
- A player fights a bandit near the village gate.
- The gate guard NPC witnesses this →
KnowledgeObservationSystemcreates knowledge: "player defeated bandit" (confidence: 1.0). - Next tick, the gate guard and merchant share the gate area.
GossipSystempropagates: "player defeated bandit" (confidence: 0.95, distortion 0.05 from high friendliness). - Later, the merchant moves to the tavern. The merchant and bartender gossip: "player defeated bandit" (confidence: 0.475, distortion 0.50 from neutral friendliness).
- When the player visits the tavern and talks to the bartender, the
EnrichedPromptBuilderinjects: "You've heard rumors that this player defeated a bandit near the gate."
Scenario: Distortion in Action¶
- An NPC observes: "wizard cast fireball at dragon" (confidence: 1.0)
- First gossip (high friendliness, distortion=0.05): confidence → 0.95
- Second gossip (neutral friendliness, distortion=0.50): confidence → 0.475
- Third gossip (neutral friendliness, distortion=0.50): confidence → 0.2375
- Fourth gossip (neutral friendliness, distortion=0.50): confidence → 0.119
- Fifth gossip (neutral friendliness, distortion=0.50): confidence → 0.059 — below propagation threshold (0.1), chain ends
With neutral friendliness, gossip can survive several hops before confidence drops below the 0.1 threshold.
Scenario: Stale Knowledge Cleanup¶
- NPC learns "stranger arrived" with TTL of 900 seconds (15 minutes)
- After 15 minutes of game time,
expire_stale()removes the entry - NPCs stop gossiping about old arrivals, keeping conversations relevant
Architecture Notes¶
The Classic RPG content pack also includes a KnowledgeGossipProcessor (in maid_classic_rpg.systems.npc.gossip) that provides directed gossip capabilities for specific NPC interactions (e.g., an NPC intentionally telling another about something). This processor is wired into SocialFabricSystem by ClassicRPGContentPack.on_load(). It uses the same KnowledgeManager.propagate() path but is triggered by NPC AI decisions rather than the automatic GossipSystem, and uses trust-based distortion instead of friendliness-based.
See Also¶
- NPC Memory System — Memory, relationships, and knowledge overview
- NPC Dialogue Guide — How knowledge appears in NPC conversations
- Creating NPCs — Setting up NPCs with archetypes and behaviors