Providers
The Pattern That Connects Everything: Providers in a Data-Driven Game Engine
In the last post, we talked about why Entity Component System architecture is worth the mental shift — how thinking in *data* instead of *objects* makes your game world composable in ways inheritance never could.
But there’s a follow-on problem ECS doesn’t automatically solve: once your world is a sea of components spread across thousands of entities, how do any of your systems make sense of it cleanly?
The answer, at least in my engine, turned out to be a single pattern I kept reaching for without fully naming it: **providers**.
The Problem: Systems Are Hungry
A game simulation has a lot of mouths to feed. At any given moment:
-
A dialogue system needs to know everything relevant about the current conversation — who the NPC is, what they’ve witnessed, what they think of the player, what the player is carrying
-
A behavior AI needs to evaluate dozens of potential actions and score them against the current world state
-
A world history pipeline needs to know what factions exist, what they believe, and what events they’ve witnessed
Each of these systems needs *contextual information*, and that information is scattered across your ECS. You could hardcode queries into each system — but then you’ve just rebuilt the spaghetti you were trying to escape.
Providers are the cleaner answer.
What a Provider Actually Is
A provider is a small, focused object with one job: look at the current world state and return a slice of relevant information in a standardized form.
It doesn’t act on anything. It doesn’t own anything. It just observes and reports.
In practice, a provider might:
-
Query the ECS for components on a specific entity
-
Check a knowledge ledger for what an NPC has witnessed
-
Read a relationship table and return a tension score
-
Inspect the player’s inventory for items with relevant properties
Each provider is narrow — it knows about one thing. But because they share a common interface, any system that needs context can simply ask: *“Run all relevant providers and give me what they find.”*
The Composability Payoff
What makes this pattern powerful isn’t any individual provider. It’s that you can compose them.
When my dialogue system builds context before handing off to the LLM, it doesn’t have one monolithic “gather everything” function. It runs a stack of providers — personality, relationship state, current activity, witnessed events, carried items — each contributing its slice. Add a new provider and every system that consumes the stack gets richer context automatically. Remove one and nothing breaks.
The same pattern feeds my behavior AI. Instead of dialogue hints, providers return scored inputs — considerations that the system uses to evaluate which action an NPC should take. The mechanism is identical; only the output shape differs.
Further out, the world history pipeline uses the same idea at a larger scale — providers that look at faction relationships, accumulated events, and cultural context to seed NPC memories and beliefs before the simulation even starts.
Same pattern. Three very different systems. Zero special-casing.
Why This Pairs With ECS
If ECS is about making your *entities* composable — snapping capabilities on and off at runtime — providers are about making your *information flow* composable. They’re the read layer that sits above the raw component data and gives systems a coherent view of what’s happening.
Together, the two patterns push in the same direction: keep data flat and decoupled, let systems compose it into meaning at the moment they need it. Neither pattern is complicated in isolation. The leverage comes from applying them consistently.
What This Unlocks
The practical benefits are less about elegance and more about velocity.
When a new system needs world context, you don’t write a new data pipeline. You write or reuse providers. When an existing provider needs to return richer data, every system consuming it benefits immediately. When you want to understand why an NPC made a decision, you can inspect exactly which providers fired and what they returned — the information flow is traceable by design.
Testing also becomes surprisingly smooth. Because each provider is a pure, narrow function — world state in, data slice out — you can test them in complete isolation. Feed it a mock ECS world with two components and assert what it returns. No scene setup, no running game loop, no trying to simulate the conditions that triggered a bug. This isn’t something I planned for upfront; it’s just a natural consequence of the pattern’s shape. Narrow inputs, predictable outputs, no side effects.
For a solo developer juggling simulation, dialogue, behavior AI, and world history in the same codebase, that combination of traceability and testability is less of a nice-to-have and more of a survival mechanism.
In future posts, I’ll go deeper on each of the systems this feeds: how providers slot into the dialogue context pipeline, how they drive NAAS behavior scoring, and how the world history enrichment pipeline uses the same idea at generation time. Each of those is its own rabbit hole — but they all share this same quiet foundation.
The pattern isn’t glamorous. It doesn’t have a dramatic origin story. It just kept being the right answer, system after system, until it became the connective tissue of the whole engine.
That tends to be how the good patterns go.